From b3eea4ee70400a519d5f8bb61058b0f4b65c60ba Mon Sep 17 00:00:00 2001 From: Moritz Kiemer Date: Wed, 8 Nov 2023 22:11:06 +0100 Subject: [PATCH] agent_based discovery: move code Change-Id: I7cc1487444c9d037a83e2f12c0469ec752fe7a2e --- cmk/base/api/agent_based/register/__init__.py | 17 +- .../api/agent_based/register/_discover.py | 123 +++++++ .../api/agent_based/register/check_plugins.py | 2 +- cmk/base/api/agent_based/register/export.py | 270 ++++---------- .../agent_based/register/section_plugins.py | 2 +- cmk/base/api/agent_based/register/utils.py | 14 +- .../agent_based_api/v1/register.py | 3 +- .../cmk/agent_based/v1/__init__.py | 2 +- .../cmk/agent_based/v1/register.py | 16 + .../cmk/agent_based/v2alpha/__init__.py | 8 + .../cmk/agent_based/v2alpha/_plugins.py | 339 ++++++++++++++++++ .../cmk/agent_based/test_v1_namespace.py | 3 +- 12 files changed, 561 insertions(+), 238 deletions(-) create mode 100644 cmk/base/api/agent_based/register/_discover.py create mode 100644 packages/cmk-agent-based/cmk/agent_based/v1/register.py create mode 100644 packages/cmk-agent-based/cmk/agent_based/v2alpha/_plugins.py diff --git a/cmk/base/api/agent_based/register/__init__.py b/cmk/base/api/agent_based/register/__init__.py index 56390d58c23..8fcdd73c185 100644 --- a/cmk/base/api/agent_based/register/__init__.py +++ b/cmk/base/api/agent_based/register/__init__.py @@ -3,11 +3,7 @@ # This file is part of Checkmk (https://checkmk.com). It is subject to the terms and # conditions defined in the file COPYING, which is part of this source code package. -import cmk.utils.debug -import cmk.utils.paths -from cmk.utils.plugin_loader import load_plugins_with_exceptions - -from cmk.base.api.agent_based.register._config import ( +from ._config import ( add_check_plugin, add_discovery_ruleset, add_host_label_ruleset, @@ -38,16 +34,7 @@ set_discovery_ruleset, set_host_label_ruleset, ) - - -def load_all_plugins() -> list[str]: - errors = [] - for plugin, exception in load_plugins_with_exceptions("cmk.base.plugins.agent_based"): - errors.append(f"Error in agent based plugin {plugin}: {exception}\n") - if cmk.utils.debug.enabled(): - raise exception - return errors - +from ._discover import load_all_plugins __all__ = [ "add_check_plugin", diff --git a/cmk/base/api/agent_based/register/_discover.py b/cmk/base/api/agent_based/register/_discover.py new file mode 100644 index 00000000000..37afe1f9209 --- /dev/null +++ b/cmk/base/api/agent_based/register/_discover.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 +# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and +# conditions defined in the file COPYING, which is part of this source code package. + + +import cmk.utils.debug +from cmk.utils.plugin_loader import load_plugins_with_exceptions + +from cmk.agent_based.v2alpha import ( + AgentSection, + CheckPlugin, + InventoryPlugin, + SimpleSNMPSection, + SNMPSection, +) + +from ._config import ( + add_check_plugin, + add_discovery_ruleset, + add_host_label_ruleset, + add_inventory_plugin, + add_section_plugin, + is_registered_check_plugin, + is_registered_inventory_plugin, + is_registered_section_plugin, +) +from .check_plugins import create_check_plugin +from .inventory_plugins import create_inventory_plugin +from .section_plugins import create_agent_section_plugin, create_snmp_section_plugin + + +def load_all_plugins() -> list[str]: + errors = [] + for plugin, exception in load_plugins_with_exceptions("cmk.base.plugins.agent_based"): + errors.append(f"Error in agent based plugin {plugin}: {exception}\n") + if cmk.utils.debug.enabled(): + raise exception + return errors + + +def register_agent_section(section: AgentSection, full_module: str) -> None: + section_plugin = create_agent_section_plugin( + name=section.name, + parsed_section_name=section.parsed_section_name, + parse_function=section.parse_function, + host_label_function=section.host_label_function, + host_label_default_parameters=section.host_label_default_parameters, + host_label_ruleset_name=section.host_label_ruleset_name, + host_label_ruleset_type=section.host_label_ruleset_type, + supersedes=section.supersedes, + full_module=full_module, + ) + + if is_registered_section_plugin(section_plugin.name): + raise ValueError(f"duplicate section definition: {section_plugin.name}") + + add_section_plugin(section_plugin) + if section_plugin.host_label_ruleset_name is not None: + add_host_label_ruleset(section_plugin.host_label_ruleset_name) + + +def register_snmp_section(section: SimpleSNMPSection | SNMPSection, full_module: str) -> None: + section_plugin = create_snmp_section_plugin( + name=section.name, + parsed_section_name=section.parsed_section_name, + parse_function=section.parse_function, + host_label_function=section.host_label_function, + host_label_default_parameters=section.host_label_default_parameters, + host_label_ruleset_name=section.host_label_ruleset_name, + host_label_ruleset_type=section.host_label_ruleset_type, + detect_spec=section.detect, + fetch=section.fetch, + supersedes=section.supersedes, + full_module=full_module, + ) + + if is_registered_section_plugin(section_plugin.name): + raise ValueError(f"duplicate section definition: {section_plugin.name}") + + add_section_plugin(section_plugin) + if section_plugin.host_label_ruleset_name is not None: + add_host_label_ruleset(section_plugin.host_label_ruleset_name) + + +def register_check_plugin(check: CheckPlugin, full_module: str) -> None: + plugin = create_check_plugin( + name=check.name, + sections=check.sections, + service_name=check.service_name, + discovery_function=check.discovery_function, + discovery_default_parameters=check.discovery_default_parameters, + discovery_ruleset_name=check.discovery_ruleset_name, + discovery_ruleset_type=check.discovery_ruleset_type, + check_function=check.check_function, + check_default_parameters=check.check_default_parameters, + check_ruleset_name=check.check_ruleset_name, + cluster_check_function=check.cluster_check_function, + full_module=full_module, + ) + + if is_registered_check_plugin(plugin.name): + raise ValueError(f"duplicate check plugin definition: {plugin.name}") + + add_check_plugin(plugin) + if plugin.discovery_ruleset_name is not None: + add_discovery_ruleset(plugin.discovery_ruleset_name) + + +def register_inventory_plugin(inventory: InventoryPlugin, full_module: str) -> None: + plugin = create_inventory_plugin( + name=inventory.name, + sections=inventory.sections, + inventory_function=inventory.inventory_function, + inventory_default_parameters=inventory.inventory_default_parameters, + inventory_ruleset_name=inventory.inventory_ruleset_name, + full_module=full_module, + ) + + if is_registered_inventory_plugin(plugin.name): + raise ValueError(f"duplicate inventory plugin definition: {plugin.name}") + + add_inventory_plugin(plugin) diff --git a/cmk/base/api/agent_based/register/check_plugins.py b/cmk/base/api/agent_based/register/check_plugins.py index 8ea4160d92c..3f841ff7860 100644 --- a/cmk/base/api/agent_based/register/check_plugins.py +++ b/cmk/base/api/agent_based/register/check_plugins.py @@ -18,13 +18,13 @@ from cmk.base.api.agent_based.register.utils import ( create_subscribed_sections, ITEM_VARIABLE, - RuleSetType, validate_default_parameters, validate_function_arguments, validate_ruleset_type, ) from cmk.agent_based.v1 import IgnoreResults, Metric, Result, Service +from cmk.agent_based.v1.register import RuleSetType MANAGEMENT_DESCR_PREFIX = "Management Interface: " diff --git a/cmk/base/api/agent_based/register/export.py b/cmk/base/api/agent_based/register/export.py index 7fa281fdaf7..091dfcf5688 100644 --- a/cmk/base/api/agent_based/register/export.py +++ b/cmk/base/api/agent_based/register/export.py @@ -4,42 +4,47 @@ # conditions defined in the file COPYING, which is part of this source code package. """All objects defined here are intended to be exposed in the API """ -from collections.abc import Callable -from dataclasses import dataclass -from typing import overload - -from cmk.utils.check_utils import ParametersTypeAlias - -from cmk.snmplib import SNMPDetectBaseType - -from cmk.base.api.agent_based.plugin_classes import ( - AgentParseFunction, - CheckFunction, - DiscoveryFunction, - HostLabelFunction, - InventoryFunction, - SimpleSNMPParseFunction, - SNMPParseFunction, +from collections.abc import Callable, Mapping +from typing import Any, overload + +from cmk.agent_based.v1 import SNMPTree +from cmk.agent_based.v1.register import RuleSetType +from cmk.agent_based.v1.type_defs import ( + CheckResult, + DiscoveryResult, + HostLabelGenerator, + InventoryResult, + StringByteTable, + StringTable, ) -from cmk.base.api.agent_based.register import ( - add_check_plugin, - add_discovery_ruleset, - add_host_label_ruleset, - add_inventory_plugin, - add_section_plugin, - is_registered_check_plugin, - is_registered_inventory_plugin, - is_registered_section_plugin, +from cmk.agent_based.v2alpha import ( + AgentSection, + CheckPlugin, + InventoryPlugin, + SimpleSNMPSection, + SNMPDetectSpecification, + SNMPSection, ) -from cmk.base.api.agent_based.register.check_plugins import create_check_plugin -from cmk.base.api.agent_based.register.inventory_plugins import create_inventory_plugin -from cmk.base.api.agent_based.register.section_plugins import ( - create_agent_section_plugin, - create_snmp_section_plugin, + +from ._discover import ( + register_agent_section, + register_check_plugin, + register_inventory_plugin, + register_snmp_section, ) -from cmk.base.api.agent_based.register.utils import get_validated_plugin_module_name, RuleSetType +from .utils import get_validated_plugin_module_name + +_ParametersTypeAlias = Mapping[str, Any] + +# This is slightly duplcated, but I don't want to expose v2 stuff in v1, not even as part of the signature. +_HostLabelFunction = Callable[..., HostLabelGenerator] + +_SNMPParseFunction = ( + Callable[[list[StringTable]], object] | Callable[[list[StringByteTable]], object] +) + +_SimpleSNMPParseFunction = Callable[[StringTable], object] | Callable[[StringByteTable], object] -from cmk.agent_based.v1 import SNMPTree __all__ = [ "agent_section", @@ -50,25 +55,14 @@ ] -@dataclass(frozen=True, kw_only=True) -class _V2AgentSection: - name: str - parse_function: AgentParseFunction | None = None - parsed_section_name: str | None = None - host_label_function: HostLabelFunction | None = None - host_label_default_parameters: ParametersTypeAlias | None = None - host_label_ruleset_name: str | None = None - host_label_ruleset_type: RuleSetType = RuleSetType.MERGED - supersedes: list[str] | None = None - - def agent_section( *, name: str, - parse_function: AgentParseFunction | None = None, + # Note: the type is left for compatibility. It actually *is* object. + parse_function: Callable[[StringTable], Any] | None = None, parsed_section_name: str | None = None, - host_label_function: HostLabelFunction | None = None, - host_label_default_parameters: ParametersTypeAlias | None = None, + host_label_function: _HostLabelFunction | None = None, + host_label_default_parameters: _ParametersTypeAlias | None = None, host_label_ruleset_name: str | None = None, host_label_ruleset_type: RuleSetType = RuleSetType.MERGED, supersedes: list[str] | None = None, @@ -116,7 +110,7 @@ def agent_section( """ return register_agent_section( - _V2AgentSection( + AgentSection( name=name, parse_function=parse_function, parsed_section_name=parsed_section_name, @@ -130,65 +124,16 @@ def agent_section( ) -def register_agent_section(section: _V2AgentSection, full_module: str) -> None: - section_plugin = create_agent_section_plugin( - name=section.name, - parsed_section_name=section.parsed_section_name, - parse_function=section.parse_function, - host_label_function=section.host_label_function, - host_label_default_parameters=section.host_label_default_parameters, - host_label_ruleset_name=section.host_label_ruleset_name, - host_label_ruleset_type=section.host_label_ruleset_type, - supersedes=section.supersedes, - full_module=full_module, - ) - - if is_registered_section_plugin(section_plugin.name): - raise ValueError(f"duplicate section definition: {section_plugin.name}") - - add_section_plugin(section_plugin) - if section_plugin.host_label_ruleset_name is not None: - add_host_label_ruleset(section_plugin.host_label_ruleset_name) - - -@dataclass(frozen=True, kw_only=True) -class _V2SimpleSnmpSection: - name: str - detect: SNMPDetectBaseType - fetch: SNMPTree - parse_function: SimpleSNMPParseFunction | None = None - parsed_section_name: str | None = None - host_label_function: HostLabelFunction | None = None - host_label_default_parameters: ParametersTypeAlias | None = None - host_label_ruleset_name: str | None = None - host_label_ruleset_type: RuleSetType = RuleSetType.MERGED - supersedes: list[str] | None = None - - -@dataclass(frozen=True, kw_only=True) -class _V2SnmpSection: - name: str - detect: SNMPDetectBaseType - fetch: list[SNMPTree] - parse_function: SNMPParseFunction | None = None - parsed_section_name: str | None = None - host_label_function: HostLabelFunction | None = None - host_label_default_parameters: ParametersTypeAlias | None = None - host_label_ruleset_name: str | None = None - host_label_ruleset_type: RuleSetType = RuleSetType.MERGED - supersedes: list[str] | None = None - - @overload # no List of trees -> SimpleSNMPParseFunction def snmp_section( *, name: str, - detect: SNMPDetectBaseType, + detect: SNMPDetectSpecification, fetch: SNMPTree, - parse_function: SimpleSNMPParseFunction | None = None, + parse_function: _SimpleSNMPParseFunction | None = None, parsed_section_name: str | None = None, - host_label_function: HostLabelFunction | None = None, - host_label_default_parameters: ParametersTypeAlias | None = None, + host_label_function: _HostLabelFunction | None = None, + host_label_default_parameters: _ParametersTypeAlias | None = None, host_label_ruleset_name: str | None = None, host_label_ruleset_type: RuleSetType = RuleSetType.MERGED, supersedes: list[str] | None = None, @@ -200,12 +145,12 @@ def snmp_section( def snmp_section( *, name: str, - detect: SNMPDetectBaseType, + detect: SNMPDetectSpecification, fetch: list[SNMPTree], - parse_function: SNMPParseFunction | None = None, + parse_function: _SNMPParseFunction | None = None, parsed_section_name: str | None = None, - host_label_function: HostLabelFunction | None = None, - host_label_default_parameters: ParametersTypeAlias | None = None, + host_label_function: _HostLabelFunction | None = None, + host_label_default_parameters: _ParametersTypeAlias | None = None, host_label_ruleset_name: str | None = None, host_label_ruleset_type: RuleSetType = RuleSetType.MERGED, supersedes: list[str] | None = None, @@ -216,12 +161,12 @@ def snmp_section( def snmp_section( *, name: str, - detect: SNMPDetectBaseType, + detect: SNMPDetectSpecification, fetch: SNMPTree | list[SNMPTree], - parse_function: SimpleSNMPParseFunction | SNMPParseFunction | None = None, + parse_function: _SimpleSNMPParseFunction | _SNMPParseFunction | None = None, parsed_section_name: str | None = None, - host_label_function: HostLabelFunction | None = None, - host_label_default_parameters: ParametersTypeAlias | None = None, + host_label_function: _HostLabelFunction | None = None, + host_label_default_parameters: _ParametersTypeAlias | None = None, host_label_ruleset_name: str | None = None, host_label_ruleset_type: RuleSetType = RuleSetType.MERGED, supersedes: list[str] | None = None, @@ -282,7 +227,7 @@ def snmp_section( """ return register_snmp_section( - _V2SnmpSection( + SNMPSection( name=name, detect=detect, fetch=fetch, @@ -295,7 +240,7 @@ def snmp_section( supersedes=supersedes, ) if isinstance(fetch, list) - else _V2SimpleSnmpSection( + else SimpleSNMPSection( name=name, detect=detect, fetch=fetch, @@ -311,55 +256,17 @@ def snmp_section( ) -def register_snmp_section(section: _V2SimpleSnmpSection | _V2SnmpSection, full_module: str) -> None: - section_plugin = create_snmp_section_plugin( - name=section.name, - parsed_section_name=section.parsed_section_name, - parse_function=section.parse_function, - host_label_function=section.host_label_function, - host_label_default_parameters=section.host_label_default_parameters, - host_label_ruleset_name=section.host_label_ruleset_name, - host_label_ruleset_type=section.host_label_ruleset_type, - detect_spec=section.detect, - fetch=section.fetch, - supersedes=section.supersedes, - full_module=full_module, - ) - - if is_registered_section_plugin(section_plugin.name): - raise ValueError(f"duplicate section definition: {section_plugin.name}") - - add_section_plugin(section_plugin) - if section_plugin.host_label_ruleset_name is not None: - add_host_label_ruleset(section_plugin.host_label_ruleset_name) - - -@dataclass(frozen=True, kw_only=True) -class _V2CheckPlugin: - name: str - sections: list[str] | None = None - service_name: str - discovery_function: DiscoveryFunction - discovery_default_parameters: ParametersTypeAlias | None = None - discovery_ruleset_name: str | None = None - discovery_ruleset_type: RuleSetType = RuleSetType.MERGED - check_function: CheckFunction - check_default_parameters: ParametersTypeAlias | None = None - check_ruleset_name: str | None = None - cluster_check_function: Callable | None = None - - def check_plugin( *, name: str, sections: list[str] | None = None, service_name: str, - discovery_function: DiscoveryFunction, - discovery_default_parameters: ParametersTypeAlias | None = None, + discovery_function: Callable[..., DiscoveryResult], + discovery_default_parameters: _ParametersTypeAlias | None = None, discovery_ruleset_name: str | None = None, discovery_ruleset_type: RuleSetType = RuleSetType.MERGED, - check_function: CheckFunction, - check_default_parameters: ParametersTypeAlias | None = None, + check_function: Callable[..., CheckResult], + check_default_parameters: _ParametersTypeAlias | None = None, check_ruleset_name: str | None = None, cluster_check_function: Callable | None = None, ) -> None: @@ -418,7 +325,7 @@ def check_plugin( """ return register_check_plugin( - _V2CheckPlugin( + CheckPlugin( name=name, sections=sections, service_name=service_name, @@ -435,45 +342,12 @@ def check_plugin( ) -def register_check_plugin(check: _V2CheckPlugin, full_module: str) -> None: - plugin = create_check_plugin( - name=check.name, - sections=check.sections, - service_name=check.service_name, - discovery_function=check.discovery_function, - discovery_default_parameters=check.discovery_default_parameters, - discovery_ruleset_name=check.discovery_ruleset_name, - discovery_ruleset_type=check.discovery_ruleset_type, - check_function=check.check_function, - check_default_parameters=check.check_default_parameters, - check_ruleset_name=check.check_ruleset_name, - cluster_check_function=check.cluster_check_function, - full_module=full_module, - ) - - if is_registered_check_plugin(plugin.name): - raise ValueError(f"duplicate check plugin definition: {plugin.name}") - - add_check_plugin(plugin) - if plugin.discovery_ruleset_name is not None: - add_discovery_ruleset(plugin.discovery_ruleset_name) - - -@dataclass(frozen=True, kw_only=True) -class _V2InventoryPlugin: - name: str - sections: list[str] | None = None - inventory_function: InventoryFunction - inventory_default_parameters: ParametersTypeAlias | None = None - inventory_ruleset_name: str | None = None - - def inventory_plugin( *, name: str, sections: list[str] | None = None, - inventory_function: InventoryFunction, - inventory_default_parameters: ParametersTypeAlias | None = None, + inventory_function: Callable[..., InventoryResult], + inventory_default_parameters: _ParametersTypeAlias | None = None, inventory_ruleset_name: str | None = None, ) -> None: """Register an inventory plugin to checkmk. @@ -505,7 +379,7 @@ def inventory_plugin( """ return register_inventory_plugin( - _V2InventoryPlugin( + InventoryPlugin( name=name, sections=sections, inventory_function=inventory_function, @@ -514,19 +388,3 @@ def inventory_plugin( ), get_validated_plugin_module_name(), ) - - -def register_inventory_plugin(inventory: _V2InventoryPlugin, full_module: str) -> None: - plugin = create_inventory_plugin( - name=inventory.name, - sections=inventory.sections, - inventory_function=inventory.inventory_function, - inventory_default_parameters=inventory.inventory_default_parameters, - inventory_ruleset_name=inventory.inventory_ruleset_name, - full_module=full_module, - ) - - if is_registered_inventory_plugin(plugin.name): - raise ValueError(f"duplicate inventory plugin definition: {plugin.name}") - - add_inventory_plugin(plugin) diff --git a/cmk/base/api/agent_based/register/section_plugins.py b/cmk/base/api/agent_based/register/section_plugins.py index bafcec05372..f6ec6737b65 100644 --- a/cmk/base/api/agent_based/register/section_plugins.py +++ b/cmk/base/api/agent_based/register/section_plugins.py @@ -29,13 +29,13 @@ SNMPSectionPlugin, ) from cmk.base.api.agent_based.register.utils import ( - RuleSetType, validate_default_parameters, validate_function_arguments, validate_ruleset_type, ) from cmk.agent_based.v1 import HostLabel, SNMPTree +from cmk.agent_based.v1.register import RuleSetType from cmk.agent_based.v1.type_defs import StringByteTable, StringTable diff --git a/cmk/base/api/agent_based/register/utils.py b/cmk/base/api/agent_based/register/utils.py index 25339fde3f7..b28d0a866b3 100644 --- a/cmk/base/api/agent_based/register/utils.py +++ b/cmk/base/api/agent_based/register/utils.py @@ -2,7 +2,6 @@ # Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 # This file is part of Checkmk (https://checkmk.com). It is subject to the terms and # conditions defined in the file COPYING, which is part of this source code package. -import enum import inspect import sys from collections.abc import Callable, Mapping, Sequence @@ -18,6 +17,8 @@ from cmk.base.api.agent_based.plugin_classes import CheckPlugin +from cmk.agent_based.v1.register import RuleSetType + TypeLabel = Literal["check", "cluster_check", "discovery", "host_label", "inventory"] ITEM_VARIABLE: Final = "%s" @@ -178,17 +179,6 @@ def _value_type(annotation: inspect.Parameter) -> bytes: return get_args(annotation)[1] -class RuleSetType(enum.Enum): - """Indicate the type of the rule set - - Discovery and host label functions may either use all rules of a rule set matching - the current host, or the merged rules. - """ - - MERGED = enum.auto() - ALL = enum.auto() - - def validate_ruleset_type(ruleset_type: RuleSetType) -> None: if not isinstance(ruleset_type, RuleSetType): allowed = ", ".join(str(c) for c in RuleSetType) diff --git a/cmk/base/plugins/agent_based/agent_based_api/v1/register.py b/cmk/base/plugins/agent_based/agent_based_api/v1/register.py index 993feece32d..8c3bad82593 100644 --- a/cmk/base/plugins/agent_based/agent_based_api/v1/register.py +++ b/cmk/base/plugins/agent_based/agent_based_api/v1/register.py @@ -10,10 +10,11 @@ agent_section, check_plugin, inventory_plugin, - RuleSetType, snmp_section, ) +from cmk.agent_based.v1.register import RuleSetType + __all__ = [ "RuleSetType", "agent_section", diff --git a/packages/cmk-agent-based/cmk/agent_based/v1/__init__.py b/packages/cmk-agent-based/cmk/agent_based/v1/__init__.py index 15197ad5bf3..cc5dbbaa3ca 100644 --- a/packages/cmk-agent-based/cmk/agent_based/v1/__init__.py +++ b/packages/cmk-agent-based/cmk/agent_based/v1/__init__.py @@ -3,7 +3,7 @@ # This file is part of Checkmk (https://checkmk.com). It is subject to the terms and # conditions defined in the file COPYING, which is part of this source code package. -from . import clusterize, render, type_defs +from . import clusterize, register, render, type_defs from ._check_levels import check_levels, check_levels_predictive from ._checking_classes import ( HostLabel, diff --git a/packages/cmk-agent-based/cmk/agent_based/v1/register.py b/packages/cmk-agent-based/cmk/agent_based/v1/register.py new file mode 100644 index 00000000000..0cb1cfcddab --- /dev/null +++ b/packages/cmk-agent-based/cmk/agent_based/v1/register.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 +# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and +# conditions defined in the file COPYING, which is part of this source code package. +import enum + + +class RuleSetType(enum.Enum): + """Indicate the type of the rule set + + Discovery and host label functions may either use all rules of a rule set matching + the current host, or the merged rules. + """ + + MERGED = enum.auto() + ALL = enum.auto() diff --git a/packages/cmk-agent-based/cmk/agent_based/v2alpha/__init__.py b/packages/cmk-agent-based/cmk/agent_based/v2alpha/__init__.py index 9e362369c97..67a8380bb38 100644 --- a/packages/cmk-agent-based/cmk/agent_based/v2alpha/__init__.py +++ b/packages/cmk-agent-based/cmk/agent_based/v2alpha/__init__.py @@ -41,10 +41,18 @@ State, TableRow, ) +from ..v1._detection import SNMPDetectSpecification # sorry from . import clusterize, render, type_defs +from ._plugins import AgentSection, CheckPlugin, InventoryPlugin, SimpleSNMPSection, SNMPSection __all__ = [ # the order is relevant for the sphinx doc! + "AgentSection", + "CheckPlugin", + "SNMPSection", + "SimpleSNMPSection", + "SNMPDetectSpecification", + "InventoryPlugin", # begin with section stuff "all_of", "any_of", diff --git a/packages/cmk-agent-based/cmk/agent_based/v2alpha/_plugins.py b/packages/cmk-agent-based/cmk/agent_based/v2alpha/_plugins.py new file mode 100644 index 00000000000..50fdd8009d5 --- /dev/null +++ b/packages/cmk-agent-based/cmk/agent_based/v2alpha/_plugins.py @@ -0,0 +1,339 @@ +#!/usr/bin/env python3 +# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 +# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and +# conditions defined in the file COPYING, which is part of this source code package. +"""All objects defined here are intended to be exposed in the API +""" + +# pylint: disable=too-many-instance-attributes + +from collections.abc import Callable, Mapping +from dataclasses import dataclass + +from ..v1 import SNMPTree +from ..v1._detection import SNMPDetectSpecification # sorry +from ..v1.register import RuleSetType +from ..v1.type_defs import ( + CheckResult, + DiscoveryResult, + HostLabelGenerator, + InventoryResult, + StringByteTable, + StringTable, +) + +InventoryFunction = Callable[..., InventoryResult] # type: ignore[misc] + +CheckFunction = Callable[..., CheckResult] # type: ignore[misc] +DiscoveryFunction = Callable[..., DiscoveryResult] # type: ignore[misc] + +AgentParseFunction = Callable[[StringTable], object] + +HostLabelFunction = Callable[..., HostLabelGenerator] # type: ignore[misc] + +SNMPParseFunction = ( + Callable[[list[StringTable]], object] | Callable[[list[StringByteTable]], object] +) + +SimpleSNMPParseFunction = Callable[[StringTable], object] | Callable[[StringByteTable], object] + + +@dataclass(frozen=True, kw_only=True) +class AgentSection: + """An AgentSection to plug into Checkmk + + The section marked by '<<>>' in the raw agent output will be processed + according to the functions and options given to this function: + + Args: + + name: The unique name of the section to be registered. + It must match the section header of the agent output ('<<>>'). + + parse_function: The function responsible for parsing the raw agent data. + It must accept exactly one argument by the name 'string_table'. + It may return an arbitrary object. Note that if the return value is + `None`, no further processing will take place (just as if the agent had + not sent any data). + This function may raise arbitrary exceptions, which will be dealt with + by the checking engine. You should expect well formatted data. + + parsed_section_name: The name under which the parsed section will be available to the plugins. + Defaults to the original name. + + host_label_function: The function responsible for extracting host labels from the parsed data. + It must accept exactly one argument by the name 'section'. + When the function is called, it will be passed the parsed data as + returned by the parse function. + It is expected to yield objects of type :class:`HostLabel`. + + host_label_default_parameters: Default parameters for the host label function. Must match + the ValueSpec of the corresponding WATO ruleset, if it exists. + + host_label_ruleset_name: The name of the host label ruleset. + + host_label_ruleset_type: The ruleset type is either :class:`RuleSetType.ALL` or + :class:`RuleSetType.MERGED`. + It describes whether this plugin needs the merged result of the + effective rules, or every individual rule matching for the current host. + + supersedes: A list of section names which are superseded by this section. If this + section will be parsed to something that is not `None` (see above) all + superseded section will not be considered at all. + + """ + + name: str + parse_function: AgentParseFunction | None = None + parsed_section_name: str | None = None + host_label_function: HostLabelFunction | None = None + host_label_default_parameters: Mapping[str, object] | None = None + host_label_ruleset_name: str | None = None + host_label_ruleset_type: RuleSetType = RuleSetType.MERGED + supersedes: list[str] | None = None + + +@dataclass(frozen=True, kw_only=True) +class SimpleSNMPSection: + """A SimpleSNMPSection to plug into Checkmk + + The snmp information will be gathered and parsed according to the functions and + options given to this function: + + Args: + + name: The unique name of the section to be registered. + + detect: The conditions on single OIDs that will result in the attempt to + fetch snmp data and discover services. + This should only match devices to which the section is applicable. + It is highly recommended to check the system description OID at the very + first, as this will make the discovery much more responsive and consume + less resources. + + fetch: The specification of snmp data that should be fetched from the device. + It must be an :class:`SNMPTree` object. + The parse function will be passed a single :class:`StringTable`. + + parse_function: The function responsible for parsing the raw snmp data. + It must accept exactly one argument by the name 'string_table'. + It will be passed a :class:`StringTable`. + It may return an arbitrary object. Note that if the return value is + `None`, no further processing will take place (just as if the agent had + not sent any data). + This function may raise arbitrary exceptions, which will be dealt with + by the checking engine. You should expect well formatted data. + + parsed_section_name: The name under which the parsed section will be available to the plugins. + Defaults to the original name. + + host_label_function: The function responsible for extracting host labels from the parsed data. + It must accept exactly one argument by the name 'section'. + When the function is called, it will be passed the parsed data as + returned by the parse function. + It is expected to yield objects of type :class:`HostLabel`. + + host_label_default_parameters: Default parameters for the host label function. Must match + the ValueSpec of the corresponding WATO ruleset, if it exists. + + host_label_ruleset_name: The name of the host label ruleset. + + host_label_ruleset_type: The ruleset type is either :class:`RuleSetType.ALL` or + :class:`RuleSetType.MERGED`. + It describes whether this plugin needs the merged result of the + effective rules, or every individual rule matching for the current host. + + supersedes: A list of section names which are superseded by this section. If this + section will be parsed to something that is not `None` (see above) all + superseded section will not be considered at all. + + """ + + name: str + detect: SNMPDetectSpecification + fetch: SNMPTree + parse_function: SimpleSNMPParseFunction | None = None + parsed_section_name: str | None = None + host_label_function: HostLabelFunction | None = None + host_label_default_parameters: Mapping[str, object] | None = None + host_label_ruleset_name: str | None = None + host_label_ruleset_type: RuleSetType = RuleSetType.MERGED + supersedes: list[str] | None = None + + +@dataclass(frozen=True, kw_only=True) +class SNMPSection: + """An SNMPSection to plug into Checkmk + + The snmp information will be gathered and parsed according to the functions and + options given to this function: + + Args: + + name: The unique name of the section to be registered. + + detect: The conditions on single OIDs that will result in the attempt to + fetch snmp data and discover services. + This should only match devices to which the section is applicable. + It is highly recommended to check the system description OID at the very + first, as this will make the discovery much more responsive and consume + less resources. + + fetch: The specification of snmp data that should be fetched from the device. + It must be a non-empty list of :class:`SNMPTree` objects. + The parse function will be passed a non-empty list of + :class:`StringTable`s accordingly. + + parse_function: The function responsible for parsing the raw snmp data. + It must accept exactly one argument by the name 'string_table'. + It will be passed either a list of :class:`StringTable`s, matching + the length of the value of the `fetch` argument. + It may return an arbitrary object. Note that if the return value is + `None`, no further processing will take place (just as if the agent had + not sent any data). + This function may raise arbitrary exceptions, which will be dealt with + by the checking engine. You should expect well formatted data. + + parsed_section_name: The name under which the parsed section will be available to the plugins. + Defaults to the original name. + + host_label_function: The function responsible for extracting host labels from the parsed data. + It must accept exactly one argument by the name 'section'. + When the function is called, it will be passed the parsed data as + returned by the parse function. + It is expected to yield objects of type :class:`HostLabel`. + + host_label_default_parameters: Default parameters for the host label function. Must match + the ValueSpec of the corresponding WATO ruleset, if it exists. + + host_label_ruleset_name: The name of the host label ruleset. + + host_label_ruleset_type: The ruleset type is either :class:`RuleSetType.ALL` or + :class:`RuleSetType.MERGED`. + It describes whether this plugin needs the merged result of the + effective rules, or every individual rule matching for the current host. + + supersedes: A list of section names which are superseded by this section. If this + section will be parsed to something that is not `None` (see above) all + superseded section will not be considered at all. + + """ + + name: str + detect: SNMPDetectSpecification + fetch: list[SNMPTree] + parse_function: SNMPParseFunction | None = None + parsed_section_name: str | None = None + host_label_function: HostLabelFunction | None = None + host_label_default_parameters: Mapping[str, object] | None = None + host_label_ruleset_name: str | None = None + host_label_ruleset_type: RuleSetType = RuleSetType.MERGED + supersedes: list[str] | None = None + + +@dataclass(frozen=True, kw_only=True) +class CheckPlugin: + """A CheckPlugin to plug into Checkmk. + + Args: + + name: The unique name of the check plugin. It must only contain the + characters 'A-Z', 'a-z', '0-9' and the underscore. + + sections: An optional list of section names that this plugin subscribes to. + They correspond to the 'parsed_section_name' specified in + :meth:`agent_section` and :meth:`snmp_section`. + The corresponding sections are passed to the discovery and check + function. The functions arguments must be called 'section_, + section_' ect. Defaults to a list containing as only element + a name equal to the name of the check plugin. + + service_name: The template for the service name. The check function must accept + 'item' as first argument if and only if "%s" is present in the value + of "service_name". + + discovery_function: The discovery_function. Arguments must be 'params' (if discovery + parameters are defined) and 'section' (if the plugin subscribes + to a single section), or 'section_, section_' ect. + corresponding to the `sections`. + It is expected to be a generator of :class:`Service` instances. + + discovery_default_parameters: Default parameters for the discovery function. Must match the + ValueSpec of the corresponding WATO ruleset, if it exists. + + discovery_ruleset_name: The name of the discovery ruleset. + + discovery_ruleset_type: The ruleset type is either :class:`RuleSetType.ALL` or + :class:`RuleSetType.MERGED`. + It describes whether this plugin needs the merged result of the + effective rules, or every individual rule matching for the current + host. + + check_function: The check_function. Arguments must be 'item' (if the service has an + item), 'params' (if check default parameters are defined) and + 'section' (if the plugin subscribes to a single section), or + 'section_, section_' ect. corresponding to the + `sections`. + + check_default_parameters: Default parameters for the check function. + Must match the ValueSpec of the corresponding WATO ruleset, if it + exists. + + check_ruleset_name: The name of the check ruleset. + + cluster_check_function: The cluster check function. If this function is not specified, the + corresponding services will not be available for clusters. + The arguments are the same as the ones for the check function, + except that the sections are dicts (node name -> node section). + + """ + + name: str + sections: list[str] | None = None + service_name: str + discovery_function: DiscoveryFunction + discovery_default_parameters: Mapping[str, object] | None = None + discovery_ruleset_name: str | None = None + discovery_ruleset_type: RuleSetType = RuleSetType.MERGED + check_function: CheckFunction + check_default_parameters: Mapping[str, object] | None = None + check_ruleset_name: str | None = None + cluster_check_function: CheckFunction | None = None + + +@dataclass(frozen=True, kw_only=True) +class InventoryPlugin: + """An InventoryPlugin to plug into Checkmk. + + Args: + + name: The unique name of the plugin. It must only contain the + characters 'A-Z', 'a-z', '0-9' and the underscore. + + sections: An optional list of section names that this plugin subscribes to. + They correspond to the 'parsed_section_name' specified in + :meth:`agent_section` and :meth:`snmp_section`. + The corresponding sections are passed to the discovery and check + function. The functions arguments must be called 'section_, + section_' ect. Defaults to a list containing as only element + a name equal to the name of the inventory plugin. + + inventory_function: The inventory_function. Arguments must be 'params' (if inventory + parameters are defined) and 'section' (if the plugin subscribes + to a single section), or 'section_, section_' ect. + corresponding to the `sections`. + It is expected to be a generator of :class:`Attributes` or + :class:`TableRow` instances. + + inventory_default_parameters: Default parameters for the inventory function. Must match the + ValueSpec of the corresponding WATO ruleset, if it exists. + + inventory_ruleset_name: The name of the inventory ruleset. + + """ + + name: str + sections: list[str] | None = None + inventory_function: InventoryFunction + inventory_default_parameters: Mapping[str, object] | None = None + inventory_ruleset_name: str | None = None diff --git a/packages/cmk-agent-based/tests/cmk/agent_based/test_v1_namespace.py b/packages/cmk-agent-based/tests/cmk/agent_based/test_v1_namespace.py index 49fdd22b780..b71b3af0712 100644 --- a/packages/cmk-agent-based/tests/cmk/agent_based/test_v1_namespace.py +++ b/packages/cmk-agent-based/tests/cmk/agent_based/test_v1_namespace.py @@ -29,7 +29,8 @@ def test_v1() -> None: # value_store: not explicitly exposed here, # not at all exposed in the old location under cmk/base/plugins/agent_based/agent_based/api "value_store", - # register: not moved to this package, b/c that is not how we're doing things anymore. + # register: only partially in this package, b/c that is not how we're doing things anymore. + "register", "Attributes", "GetRateError", "HostLabel",