diff --git a/README.md b/README.md index 50601b065f7b566fbf43220cd2409122bc2771d7..e5fe350ff99db0d635c5c281be57512bc40a6807 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/cisco_meraki-1.3.2-20240626.mkp "cisco_meraki-1.3.2-20240626.mkp" +[PACKAGE]: ../../raw/master/mkp/cisco_meraki-1.3.2-20240660.mkp "cisco_meraki-1.3.2-20240660.mkp" [SDK]: ../../raw/master/mkp/MerakiSDK-1.46.0-20240516.mkp "MerakiSDK-1.46.0-20240516.mkpp" # Cisco Meraki special agent diff --git a/mkp/cisco_meraki-1.3.2-20240660.mkp b/mkp/cisco_meraki-1.3.2-20240660.mkp new file mode 100644 index 0000000000000000000000000000000000000000..973807245ac69b9b0c0cf90ea75fc8a37f8a4896 Binary files /dev/null and b/mkp/cisco_meraki-1.3.2-20240660.mkp differ diff --git a/source/agent_based/cisco_meraki_org_device_info.py b/source/agent_based/cisco_meraki_org_device_info.py index d02a9118eec75ab77e5b2a5e2dd8e2adf06cb1d9..48aacc90a255b41f987aba99fcb7114a4d5fdd26 100644 --- a/source/agent_based/cisco_meraki_org_device_info.py +++ b/source/agent_based/cisco_meraki_org_device_info.py @@ -13,7 +13,7 @@ from dataclasses import dataclass from cmk.base.plugins.agent_based.agent_based_api.v1 import Attributes, register, TableRow, HostLabel from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import InventoryResult, StringTable, HostLabelGenerator -from cmk.base.plugins.agent_based.utils.cisco_meraki import load_json, MerakiAPIData +from cmk_addons.plugins.meraki.lib.utils import load_json, MerakiAPIData @dataclass(frozen=True) @@ -33,7 +33,7 @@ class DeviceInfo: @classmethod def parse(cls, row: MerakiAPIData) -> "DeviceInfo": return cls( - # Some entries may missing in older API versions + # Some entries may be missed in older API versions product=str(row.get("productType", "")), serial=str(row["serial"]), model=str(row["model"]), diff --git a/source/agent_based/cisco_meraki_org_device_status.py b/source/agent_based/cisco_meraki_org_device_status.py index 48ebb619e335144ad16e4f231c77d08df1a0a623..56b1c6feff8c3d562e4fd8c93c704a8ac9e0efc5 100644 --- a/source/agent_based/cisco_meraki_org_device_status.py +++ b/source/agent_based/cisco_meraki_org_device_status.py @@ -36,11 +36,7 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( StringTable, ) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - check_last_reported_ts, - load_json, - MerakiAPIData, -) +from cmk_addons.plugins.meraki.lib.utils import MerakiAPIData, check_last_reported_ts, load_json @dataclass(frozen=True) diff --git a/source/agent_based/cisco_meraki_org_licenses_overview.py b/source/agent_based/cisco_meraki_org_licenses_overview.py index 108c7ca9d61246f8a4a7509ca9796cf16b41424c..095d28ed2753c5e26371fe2f609cfe2c9d65d1e0 100644 --- a/source/agent_based/cisco_meraki_org_licenses_overview.py +++ b/source/agent_based/cisco_meraki_org_licenses_overview.py @@ -37,10 +37,7 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( StringTable, InventoryResult, ) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - load_json, - MerakiAPIData, -) +from cmk_addons.plugins.meraki.lib.utils import MerakiAPIData, add_org_id_name_to_output, load_json @dataclass(frozen=True) @@ -120,6 +117,13 @@ def check_licenses_overview( if (item_data := section.get(params.get('internal_item_name', item))) is None: return + yield from add_org_id_name_to_output( + item_data.organisation_id, + item_data.organisation_name, + params['item_variant'], + params.get('dont_show_alias_on_info'), + ) + yield Result( state=State.OK if item_data.status == "OK" else State(params['state_license_not_ok']), summary=f"Status: {item_data.status}", @@ -132,33 +136,10 @@ def check_licenses_overview( licensed_devices = sum(item_data.licensed_device_counts.values()) yield Result( state=State.OK, - summary=f'Number of licensed devices: {licensed_devices}' + summary=f'Licensed devices: {licensed_devices}' ) yield Metric(value=licensed_devices, name='sum_licensed_devices') - org_id = f'ID: {item_data.organisation_id}' - org_name = f'Name: {item_data.organisation_name}' - org_id_notice = f'Organisation ID: {item_data.organisation_id}' - org_name_notice = f'Organisation name: {item_data.organisation_name}' - - match params['item_variant']: - case 'org_id': - yield Result(state=State.OK, notice=org_id_notice) - if params.get('dont_show_alias_on_info'): - yield Result(state=State.OK, notice=org_name_notice) - else: - yield Result(state=State.OK, summary=org_name) - case 'org_name': - if params.get('dont_show_alias_on_info'): - yield Result(state=State.OK, notice=org_id_notice) - else: - yield Result(state=State.OK, summary=org_id) - yield Result(state=State.OK, notice=org_name_notice) - - case _: - yield Result(state=State.OK, notice=org_id_notice) - yield Result(state=State.OK, notice=org_name_notice) - for device_type, device_count in sorted(item_data.licensed_device_counts.items(), key=lambda t: t[0], ): yield Result(state=State.OK, notice=f"{device_type}: {device_count} licensed devices") @@ -192,11 +173,17 @@ def _check_expiration_date( else: yield from check_levels( - age, + value=age, levels_lower=levels_lower, label="Remaining time", render_func=render.timespan, - metric_name="remaining_time" + # metric_name="remaining_time" + ) + # needed as levels don't go the graphing system + yield Metric( + value=age, + name='remaining_time', + levels=levels_lower ) @@ -219,7 +206,7 @@ register.check_plugin( # # inventory license overview # -# ToDo: add senors (MS) +# ToDo: add senors (MT) -> do the need a license? -> done def inventory_licenses_overview(section: Section | None) -> InventoryResult: path = ['software', 'applications', 'cisco_meraki', 'licenses'] for org_id, org_data in section.items(): @@ -233,12 +220,16 @@ def inventory_licenses_overview(section: Section | None) -> InventoryResult: licenses.update({'ms': licenses.get('ms', 0) + device_count}) elif device_type.lower().startswith('mv'): # video / camera licenses.update({'mv': licenses.get('mv', 0) + device_count}) + elif device_type.lower().startswith('mt'): # sensors + licenses.update({'mt': licenses.get('mt', 0) + device_count}) elif device_type.lower().startswith('mr'): # access points licenses.update({'mr': licenses.get('mr', 0) + device_count}) elif device_type.lower().startswith('wireless'): # merge with access points licenses.update({'mr': licenses.get('mr', 0) + device_count}) + elif device_type.lower().startswith('sm'): # systems manager + licenses.update({'sm': licenses.get('sm', 0) + device_count}) else: # fallback for unknown device type - licenses.update({device_type.lower(): device_count}) + licenses.update({device_type.lower(): licenses.get(device_type.lower(), 0) + device_count}) licenses.update({'summary': sum(org_data.licensed_device_counts.values())}) yield TableRow( diff --git a/source/agent_based/cisco_meraki_org_sensor_readings.py_ b/source/agent_based/cisco_meraki_org_sensor_readings.py_ new file mode 100644 index 0000000000000000000000000000000000000000..10e9d86ee5f997c98304d789cfeec045e3a2cf35 --- /dev/null +++ b/source/agent_based/cisco_meraki_org_sensor_readings.py_ @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (C) 2022 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. + +# enhancements by thl-cmk[at]outlook[dot]com, https://thl-cmk.hopto.org +# 2023-11-10: removed ts check/sort, we get always only the last reading, so need to sort by last reported +# added ability to handle temperature, humidity and battery at the same time, not sure if there +# is a need to add some index (multiple temperature reading for example) +# added battery/humidity check +# changed section from Sequence to Mapping by metric + + +from dataclasses import dataclass +from datetime import datetime +from collections.abc import Sequence, Mapping + +from cmk.base.plugins.agent_based.agent_based_api.v1 import get_value_store, register, Service, render +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import CheckResult, DiscoveryResult, StringTable +from cmk.base.plugins.agent_based.utils.temperature import check_temperature, TempParamType, check_levels +from cmk_addons.plugins.meraki.lib.utils import check_last_reported_ts, load_json, MerakiAPIData + + +@dataclass(frozen=True) +class SensorReadings: + metric: str + unit: str + last_reported: datetime | None = None + reading: float | None = None + + @classmethod + def parse(cls, row: MerakiAPIData) -> Sequence["SensorReadings"] | None: + if not isinstance(raw_readings := row.get("readings"), list): + return None + + # not needed, we have only the last reading. + # if not ( + # readings_by_datetime := { + # reading_datetime: raw_reading + # for raw_reading in raw_readings + # if (reading_datetime := cls._parse_ts(raw_reading["ts"])) is not None + # } + # ): + # return None + # last_reported, readings = sorted(readings_by_datetime.items(), key=lambda t: t[0], reverse=True)[0] + + parsed_readings = {} + for raw_reading in raw_readings: + sensor_type = raw_reading['metric'] + match sensor_type: + case 'battery': + sensor_unit = 'percentage' + case 'humidity': + sensor_unit = 'relativePercentage' + case 'temperature': + sensor_unit = 'celsius' + case _: + return None + + parsed_readings.update({sensor_type: cls( + last_reported=cls._parse_ts(raw_reading["ts"]), + metric=sensor_type, + unit=sensor_unit, + reading=cls._parse_reading( + reading=raw_reading, + sensor_type=sensor_type, + sensor_unit=sensor_unit, + ), + )}) + + return parsed_readings + + @staticmethod + def _parse_ts(raw_ts: str) -> datetime | None: + try: + return datetime.strptime(raw_ts, "%Y-%m-%dT%H:%M:%SZ") + except ValueError: + return None + + @staticmethod + def _parse_reading(reading: MerakiAPIData, sensor_type: str, sensor_unit: str) -> float | None: + try: + sensor_data = reading[sensor_type] + except KeyError: + return None + + if not isinstance(sensor_data, dict): + return None + + try: + return float(sensor_data[sensor_unit]) + except (KeyError, ValueError): + return None + + +def parse_sensor_readings(string_table: StringTable) -> SensorReadings | None: + return ( + SensorReadings.parse(loaded_json[0]) if (loaded_json := load_json(string_table)) else None + ) + + +register.agent_section( + name="cisco_meraki_org_sensor_readings", + parse_function=parse_sensor_readings, +) + + +def discover_sensor_temperature( + section: Mapping[SensorReadings] | None, +) -> DiscoveryResult: + if 'temperature' in section.keys(): + yield Service(item='Temperature') + + +def check_sensor_temperature( + item: str, + params: TempParamType, + section: Mapping[SensorReadings] | None, +) -> CheckResult: + try: + reading = section[item.lower()] + except KeyError: + return None + + yield from check_temperature( + reading=reading.reading, + params=params, + unique_name=item, + value_store=get_value_store(), + ) + + if reading.last_reported is not None: + yield from check_last_reported_ts( + last_reported_ts=reading.last_reported.timestamp(), + as_metric=False, + ) + + +register.check_plugin( + name="cisco_meraki_org_sensor_temperature", + sections=["cisco_meraki_org_sensor_readings"], + service_name="Sensor %s", + discovery_function=discover_sensor_temperature, + check_function=check_sensor_temperature, + check_ruleset_name="temperature", + check_default_parameters={ + # "levels": (50.0, 60.0), + }, +) + + +def discover_sensor_humidity( + section: Mapping[SensorReadings] | None, +) -> DiscoveryResult: + if 'humidity' in section.keys(): + yield Service(item='Humidity') + + +def check_sensor_humidity( + item: str, + params: TempParamType, + section: Mapping[SensorReadings] | None, +) -> CheckResult: + try: + reading = section[item.lower()] + except KeyError: + return None + + yield from check_levels( + value=reading.reading, + label="Relative Humidity", + levels_upper=params.get('levels_upper', None), + metric_name='humidity', + render_func=render.percent + ) + + if reading.last_reported is not None: + yield from check_last_reported_ts( + last_reported_ts=reading.last_reported.timestamp(), + as_metric=False, + ) + + +register.check_plugin( + name="cisco_meraki_org_sensor_humidity", + sections=["cisco_meraki_org_sensor_readings"], + service_name="Sensor %s", + discovery_function=discover_sensor_humidity, + check_function=check_sensor_humidity, + check_ruleset_name="humidity", + check_default_parameters={ + # "levels": (50.0, 60.0), + }, +) + + +def discover_sensor_battery( + section: Mapping[SensorReadings] | None, +) -> DiscoveryResult: + if 'battery' in section.keys(): + yield Service(item='Battery') + + +def check_sensor_battery( + item: str, + params: TempParamType, + section: Mapping[SensorReadings] | None, +) -> CheckResult: + try: + reading = section[item.lower()] + except KeyError: + return None + + yield from check_levels( + value=reading.reading, + label='Battery level', + levels_upper=params.get('levels_upper', None), + metric_name='battery', + render_func=render.percent + ) + + if reading.last_reported is not None: + yield from check_last_reported_ts( + last_reported_ts=reading.last_reported.timestamp(), + as_metric=False, + ) + + +register.check_plugin( + name="cisco_meraki_org_sensor_battery", + sections=["cisco_meraki_org_sensor_readings"], + service_name="Sensor %s", + discovery_function=discover_sensor_battery, + check_function=check_sensor_battery, + check_ruleset_name="battery", + check_default_parameters={ + # "levels": (50.0, 60.0), + }, +) diff --git a/source/checks/agent_cisco_meraki b/source/checks/agent_cisco_meraki deleted file mode 100644 index 74230b874006e58d5f574213ebafe09fbab2a5e0..0000000000000000000000000000000000000000 --- a/source/checks/agent_cisco_meraki +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# Copyright (C) 2022 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. - -# enhancements by thl-cmk[at]outlook[dot]com, https://thl-cmk.hopto.org -# - added host_suffix_prefix option -# - added no-cache option -# 2023-11-18: changed from section to excluded_sections -# 2023-11-22: replaced host_suffix_prefix option by org_id_as_prefix -# 2024-06-23: added cache time per section -> not nice but should work. - -# -# needs to be re implemented for CMK 2.3.X -# https://github.com/Checkmk/checkmk/commit/c12cca9fe631d935ed5f239c23288ea856869e6e -# - -from collections.abc import Mapping, Sequence -from typing import Any - - -def agent_cisco_meraki_arguments( - params: Mapping[str, Any], - hostname: str, - ipaddress: str | None, -) -> Sequence[object]: - args = [ - hostname, - passwordstore_get_cmdline("%s", params["api_key"]), - ] - - if (proxy := params.get("proxy")) is not None: - args.extend( - [ - "--proxy", - get_http_proxy(proxy).serialize(), - ] - ) - - if orgs := params.get("orgs"): - args.extend(["--orgs"] + orgs) - - if params.get("no_cache"): - args.append('--no-cache') - - if params.get("org_id_as_prefix"): - args.append('--org-id-as-prefix') - - if excluded_sections := params.get("excluded_sections"): - args.extend(["--excluded-sections"] + excluded_sections) - - if cache_per_section := params.get("cache_per_section"): - args.extend(["--cache-per-section"] + list(cache_per_section)) - - return args - - -special_agent_info["cisco_meraki"] = agent_cisco_meraki_arguments diff --git a/source/agent_based/cisco_meraki_org_appliance_performance.py b/source/cmk_addons_plugins/meraki/agent_based/appliance_performance.py similarity index 65% rename from source/agent_based/cisco_meraki_org_appliance_performance.py rename to source/cmk_addons_plugins/meraki/agent_based/appliance_performance.py index 1ce4e1d78f52f290058328542238e52f94e444e9..fce7cf5e09bd0acc4acffd603e958bf9f22c5c82 100644 --- a/source/agent_based/cisco_meraki_org_appliance_performance.py +++ b/source/cmk_addons_plugins/meraki/agent_based/appliance_performance.py @@ -5,33 +5,35 @@ # # Author: thl-cmk[at]outlook[dot]com # URL : https://thl-cmk.hopto.org -# Date : 2023-11-04 +# Date : 2024-06-20 # File : cisco_meraki_appliance_performance.py (check plugin) +# 2024-06-29: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_org_appliance_performance.py in to appliance_performance.py -from _collections_abc import Mapping +from collections.abc import Mapping -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - check_levels, - Service, - render, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, CheckResult, DiscoveryResult, + Service, StringTable, + check_levels, + render, ) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - load_json, -) + +from cmk_addons.plugins.meraki.lib.utils import load_json + # sample agent output # {"perfScore": 1} # sample string_table # [['[{"perfScore": 0}]']] -def parse_appliance_performance(string_table: StringTable) -> int: + +def parse_appliance_performance(string_table: StringTable) -> int | None: json_data = load_json(string_table) if (json_data := json_data[0]) is None: return @@ -41,7 +43,7 @@ def parse_appliance_performance(string_table: StringTable) -> int: return int(perfscore) -register.agent_section( +agent_section_meraki_org_appliance_performance = AgentSection( name="cisco_meraki_org_appliance_performance", parse_function=parse_appliance_performance, ) @@ -55,20 +57,20 @@ def check_appliance_performance(params: Mapping[str, any], section: int) -> Chec yield from check_levels( value=section, label='Utilization', - levels_upper=params.get('levels_upper'), + levels_upper=params['levels_upper'], render_func=render.percent, metric_name='utilization', boundaries=(0, 100), ) -register.check_plugin( - name='cisco_meraki_org_appliance_performance', - service_name='Utilization', - discovery_function=discover_appliance_performance, +check_plugin_meraki_org_appliance_performance = CheckPlugin( + name="cisco_meraki_org_appliance_performance", + service_name="Utilization", check_function=check_appliance_performance, + discovery_function=discover_appliance_performance, + check_ruleset_name="cisco_meraki_org_appliance_performance", check_default_parameters={ - 'levels_upper': (60, 80), + 'levels_upper': ('fixed', (60, 80)), }, - check_ruleset_name='cisco_meraki_org_appliance_performance', ) diff --git a/source/agent_based/cisco_meraki_org_appliance_uplinks.py b/source/cmk_addons_plugins/meraki/agent_based/appliance_uplinks.py similarity index 86% rename from source/agent_based/cisco_meraki_org_appliance_uplinks.py rename to source/cmk_addons_plugins/meraki/agent_based/appliance_uplinks.py index 5bfe043668641ede214c5c2394bd2636dcbf8d98..6879d6c272ba4371dbc038ea208aa6620be00813 100644 --- a/source/agent_based/cisco_meraki_org_appliance_uplinks.py +++ b/source/cmk_addons_plugins/meraki/agent_based/appliance_uplinks.py @@ -15,28 +15,29 @@ # moved parse function to the dataclasses # 2024-05-19: reworked appliance uplinks usage # 2024-04-24: fixed, we can have no traffic if uplinc is not connected +# 2024-06-29: refactored for CMK 2.3 +# changed service name from "Appliance Uplink" to "Uplink" +# fixed render function for bandwidth -> uses now render.networkbandwidth +# 2024-06-30: renamed from cisco_meraki_org_appliance_uplinks.py in to appliance_uplinks.py +from collections.abc import Mapping from dataclasses import dataclass from datetime import datetime -from _collections_abc import Mapping -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, Result, Service, State, + StringTable, check_levels, - register, render, ) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - CheckResult, - DiscoveryResult, - StringTable, -) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - get_int, # type: ignore[import] - load_json, -) + +from cmk_addons.plugins.meraki.lib.utils import get_int, load_json # sample string_table __appliance_uplinks = [ @@ -150,7 +151,7 @@ def parse_appliance_uplinks(string_table: StringTable) -> Appliance | None: return Appliance.parse(json_data[0]) -register.agent_section( +agent_section_cisco_meraki_org_appliance_uplinks = AgentSection( name="cisco_meraki_org_appliance_uplinks", parse_function=parse_appliance_uplinks, ) @@ -164,21 +165,23 @@ def discover_appliance_uplinks(section: Appliance) -> DiscoveryResult: _STATUS_MAP = { "active": 0, "failed": 2, - "not connected": 1, + "not_connected": 1, "ready": 0, } - _TIMESPAN = 60 +def render_network_bandwidth_bits(value: int) -> str: + return render.networkbandwidth(value/8) + + def check_appliance_uplinks(item: str, params: Mapping[str, any], section: Appliance) -> CheckResult: - try: - uplink: ApplianceUplink = section.uplinks[item] - except KeyError: + if (uplink := section.uplinks.get(item)) is None: return None if params.get('status_map'): _STATUS_MAP.update(params['status_map']) + _STATUS_MAP['not connected'] = _STATUS_MAP['not_connected'] # can not use 'nor connected' in params anymore :-( yield Result(state=State(_STATUS_MAP.get(uplink.status, 3)), summary=f'Status: {uplink.status}') if uplink.ip: @@ -187,15 +190,14 @@ def check_appliance_uplinks(item: str, params: Mapping[str, any], section: Appli yield Result(state=State.OK, summary=f'Public IP: {uplink.public_ip}') yield Result(state=State.OK, notice=f'Network: {section.network_name}') - if uplink.status in ['active']: # we can only have traffic, if uplinc is connected + if params.get('show_traffic') and uplink.status in ['active']: # we can only have traffic, if uplink is connected if uplink.received: # and params.get('show_traffic'): value = uplink.received * 8 / _TIMESPAN # Bits / Timespan yield from check_levels( value=value, # Bits label='In', metric_name='if_in_bps', - render_func=lambda v: render.networkbandwidth(v/8), # Bytes - # notice_only=True, + render_func=render_network_bandwidth_bits, # Bytes ) if uplink.sent: # and params.get('show_traffic'): @@ -204,8 +206,7 @@ def check_appliance_uplinks(item: str, params: Mapping[str, any], section: Appli value=value, # Bits label='Out', metric_name='if_out_bps', - render_func=lambda v: render.networkbandwidth(v/8), # Bytes - # notice_only=True, + render_func=render_network_bandwidth_bits, # Bytes ) # not needed, will show in device status (?) @@ -225,9 +226,9 @@ def check_appliance_uplinks(item: str, params: Mapping[str, any], section: Appli yield Result(state=State.OK, notice=f'Secondary DNS: {uplink.secondary_dns}') -register.check_plugin( +check_plugin_cisco_meraki_org_appliance_uplinks = CheckPlugin( name='cisco_meraki_org_appliance_uplinks', - service_name='Appliance Uplink %s', + service_name='Uplink %s', discovery_function=discover_appliance_uplinks, check_function=check_appliance_uplinks, check_default_parameters={}, diff --git a/source/agent_based/cisco_meraki_org_appliance_vpns.py b/source/cmk_addons_plugins/meraki/agent_based/appliance_vpns.py similarity index 91% rename from source/agent_based/cisco_meraki_org_appliance_vpns.py rename to source/cmk_addons_plugins/meraki/agent_based/appliance_vpns.py index 25b76d177ad5e3e4413a09f7c24f06bd6deb18ac..e0856fee41dbc023ad918fa851292fc64dfe3f0a 100644 --- a/source/agent_based/cisco_meraki_org_appliance_vpns.py +++ b/source/cmk_addons_plugins/meraki/agent_based/appliance_vpns.py @@ -10,25 +10,26 @@ # 2024-04-27: made data parsing more robust # 2024-05-15: moved parse function to data classes +# 2024-06-29: refactored for CMK 2.3 +# changed service name from "Appliance VPN" to "VPN peer" +# 2024-06-30: renamed from cisco_meraki_org_appliance_vpns.py in to appliance_vpns.py from abc import abstractmethod +from collections.abc import Mapping, Sequence from dataclasses import dataclass -from _collections_abc import Mapping, Sequence -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, Result, Service, State, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - CheckResult, - DiscoveryResult, StringTable, ) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - load_json, -) + +from cmk_addons.plugins.meraki.lib.utils import load_json # sample string_table __appliance_vpn_statuses = [ @@ -163,7 +164,7 @@ def parse_appliance_vpns(string_table: StringTable) -> Mapping[str, ApplianceVPN return meraki_peers -register.agent_section( +agent_section_cisco_meraki_org_appliance_vpns = AgentSection( name="cisco_meraki_org_appliance_vpns", parse_function=parse_appliance_vpns, ) @@ -175,9 +176,7 @@ def discover_appliance_vpns(section: Mapping[str, ApplianceVPNPeer]) -> Discover def check_appliance_vpns(item: str, params: Mapping[str, any], section: Mapping[str, ApplianceVPNPeer]) -> CheckResult: - try: - peer: ApplianceVPNPeer = section[item] - except KeyError: + if (peer := section.get(item)) is None: return None if peer.reachability is not None and peer.reachability.lower() in ['reachable']: @@ -198,9 +197,9 @@ def check_appliance_vpns(item: str, params: Mapping[str, any], section: Mapping[ yield Result(state=State.OK, notice=f'name: {uplink.interface}, public IP: {uplink.public_ip}') -register.check_plugin( +check_plugin_cisco_meraki_org_appliance_vpns = CheckPlugin( name='cisco_meraki_org_appliance_vpns', - service_name='Appliance VPN %s', + service_name='VPN peer %s', discovery_function=discover_appliance_vpns, check_function=check_appliance_vpns, check_default_parameters={}, diff --git a/source/agent_based/cisco_meraki_org_cellular_uplinks.py b/source/cmk_addons_plugins/meraki/agent_based/cellular_uplinks.py similarity index 59% rename from source/agent_based/cisco_meraki_org_cellular_uplinks.py rename to source/cmk_addons_plugins/meraki/agent_based/cellular_uplinks.py index 0261165027847b69bbcea9285418381b5927d8c4..06c149e54fad1d1dd7dbcca76e1dd90ceb8e4996 100644 --- a/source/agent_based/cisco_meraki_org_cellular_uplinks.py +++ b/source/cmk_addons_plugins/meraki/agent_based/cellular_uplinks.py @@ -9,30 +9,63 @@ # File : cisco_meraki_org_cellular_uplinks.py (check plugin) # 2024-04-27: made data parsing more robust +# 2024-06-29: refactored for CMK 2.3 +# moved parse functions to class methods +# changed service name from "Cellular uplink" to "Uplink" +# 2024-06-30: renamed from cisco_meraki_org_cellular_uplinks.py in to cellular_uplinks.py - -from _collections_abc import Mapping +from collections.abc import Mapping from dataclasses import dataclass from datetime import datetime -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, Metric, Result, Service, State, - register, - render, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - CheckResult, - DiscoveryResult, StringTable, + render, ) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - get_int, # type: ignore[import] - load_json, -) +from cmk_addons.plugins.meraki.lib.utils import get_int, load_json + +__cellular_uplinks = [ + { + "highAvailability": { + "enabled": False, + "role": "primary" + }, + "lastReportedAt": "2023-11-13T19:52:06Z", + "model": "MG41", + "networkId": "L_575897802350012343", + "serial": "QQQQ-XXXX-ZZZZ", + "uplinks": [ + { + "apn": "apn.name", + "connectionType": "lte", + "dns1": None, + "dns2": None, + "gateway": None, + "iccid": "89492027206012345518", + "interface": "cellular", + "ip": None, + "model": "integrated", + "provider": "provider.name", + "publicIp": "2.3.4.5", + "signalStat": { + "rsrp": "-111", + "rsrq": "-8" + }, + "signalType": None, + "status": "active" + } + ] + } +] _LAST_REPORTED_AT = "%Y-%m-%dT%H:%M:%SZ" @@ -57,12 +90,41 @@ class CellularUplink: signal_type: str | None status: str | None + @classmethod + def parse(cls, uplink: Mapping[str, object]): + return cls( + apn=str(uplink['apn']) if uplink.get('apn') is not None else None, + connection_type=str(uplink['connectionType']) if uplink.get('connectionType') is not None else None, + dns1=str(uplink['dns1']) if uplink.get('dns1') is not None else None, + dns2=str(uplink['dns2']) if uplink.get('dns2') is not None else None, + gateway=str(uplink['gateway']) if uplink.get('gateway') is not None else None, + iccid=str(uplink['iccid']) if uplink.get('iccid') is not None else None, + interface=str(uplink['interface']) if uplink.get('interface') is not None else None, + ip=str(uplink['ip']) if uplink.get('ip') is not None else None, + model=str(uplink['model']) if uplink.get('model') is not None else None, + provider=str(uplink['provider']) if uplink.get('provider') is not None else None, + public_ip=str(uplink['publicIp']) if uplink.get('publicIp') is not None else None, + signal_type=str(uplink['signalType']) if uplink.get('signalType') is not None else None, + status=str(uplink['status']) if uplink.get('status') is not None else None, + rsrp=get_int(uplink.get('signalStat', {}).get('rsrp')), + rsrq=get_int(uplink.get('signalStat', {}).get('rsrq')), + received=get_int(uplink.get('received')), + sent=get_int(uplink.get('sent')), + ) + @dataclass(frozen=True) class CellularUplinkHA: enabled: bool | None role: str | None + @classmethod + def parse(cls, high_availability: Mapping[str, object]): + return cls( + enabled=bool(high_availability['enabled']) if high_availability.get('enabled') is not None else None, + role=str(high_availability['role']) if high_availability.get('enabled') is not None else None, + ) + @dataclass(frozen=True) class CellularGateway: @@ -73,82 +135,30 @@ class CellularGateway: serial: str | None uplinks: Mapping[str, CellularUplink] | None - -__cellular_uplinks = [ - { - "highAvailability": { - "enabled": False, - "role": "primary" - }, - "lastReportedAt": "2023-11-13T19:52:06Z", - "model": "MG41", - "networkId": "L_575897802350012343", - "serial": "QQQQ-XXXX-ZZZZ", - "uplinks": [ - { - "apn": "apn.name", - "connectionType": "lte", - "dns1": None, - "dns2": None, - "gateway": None, - "iccid": "89492027206012345518", - "interface": "cellular", - "ip": None, - "model": "integrated", - "provider": "provider.name", - "publicIp": "2.3.4.5", - "signalStat": { - "rsrp": "-111", - "rsrq": "-8" - }, - "signalType": None, - "status": "active" - } - ] - } -] + @classmethod + def parse(cls, cellular_gateway): + return cls( + serial=str(cellular_gateway['serial']) if cellular_gateway.get('serial') is not None else None, + model=str(cellular_gateway['model']) if cellular_gateway.get('model') is not None else None, + last_reported_at=datetime.strptime( + cellular_gateway['lastReportedAt'], _LAST_REPORTED_AT) if cellular_gateway.get( + 'lastReportedAt' + ) is not None else None, + # network_name=str(json_data['networkName']) if json_data.get('networkName') is not None else None, + high_availability=CellularUplinkHA.parse(cellular_gateway.get('highAvailability', {})), + uplinks={ + uplink['interface']: CellularUplink.parse(uplink) for uplink in cellular_gateway.get('uplinks', []) + }, + ) def parse_cellular_uplinks(string_table: StringTable) -> CellularGateway | None: json_data = load_json(string_table) json_data = json_data[0] - return CellularGateway( - serial=str(json_data['serial']) if json_data.get('serial') is not None else None, - model=str(json_data['model']) if json_data.get('model') is not None else None, - last_reported_at=datetime.strptime(json_data['lastReportedAt'], _LAST_REPORTED_AT) if json_data.get( - 'lastReportedAt') is not None else None, - # network_name=str(json_data['networkName']) if json_data.get('networkName') is not None else None, - high_availability=CellularUplinkHA( - enabled=bool(json_data['highAvailability']['enabled']) if json_data.get('highAvailability', {}).get( - 'enabled') is not None else None, - role=str(json_data['highAvailability']['role']) if json_data.get('highAvailability', {}).get( - 'enabled') is not None else None, - ), - uplinks={ - uplink['interface']: CellularUplink( - apn=str(uplink['apn']) if uplink.get('apn') is not None else None, - connection_type=str(uplink['connectionType']) if uplink.get('connectionType') is not None else None, - dns1=str(uplink['dns1']) if uplink.get('dns1') is not None else None, - dns2=str(uplink['dns2']) if uplink.get('dns2') is not None else None, - gateway=str(uplink['gateway']) if uplink.get('gateway') is not None else None, - iccid=str(uplink['iccid']) if uplink.get('iccid') is not None else None, - interface=str(uplink['interface']) if uplink.get('interface') is not None else None, - ip=str(uplink['ip']) if uplink.get('ip') is not None else None, - model=str(uplink['model']) if uplink.get('model') is not None else None, - provider=str(uplink['provider']) if uplink.get('provider') is not None else None, - public_ip=str(uplink['publicIp']) if uplink.get('publicIp') is not None else None, - signal_type=str(uplink['signalType']) if uplink.get('signalType') is not None else None, - status=str(uplink['status']) if uplink.get('status') is not None else None, - rsrp=get_int(uplink.get('signalStat', {}).get('rsrp')), - rsrq=get_int(uplink.get('signalStat', {}).get('rsrq')), - received=get_int(uplink.get('received')), - sent=get_int(uplink.get('sent')), - ) for uplink in json_data.get('uplinks', []) - }, - ) + return CellularGateway.parse(json_data) -register.agent_section( +agent_section_cisco_meraki_org_cellular_uplinks = AgentSection( name="cisco_meraki_org_cellular_uplinks", parse_function=parse_cellular_uplinks, ) @@ -160,10 +170,8 @@ def discover_cellular_uplinks(section: CellularGateway) -> DiscoveryResult: def check_cellular_uplinks(item: str, params: Mapping[str, any], section: CellularGateway) -> CheckResult: - try: - uplink: CellularUplink = section.uplinks[item] - except KeyError: - return None + if (uplink := section.uplinks.get(item)) is None: + return if uplink.status not in ['active']: yield Result(state=State(params.get('status_not_active', 1)), summary=f'Status: {uplink.status}') @@ -206,9 +214,9 @@ def check_cellular_uplinks(item: str, params: Mapping[str, any], section: Cellul yield Result(state=State.OK, notice=f'DNS 2: {uplink.dns2}') -register.check_plugin( +check_plugin_cisco_meraki_org_cellular_uplinks = CheckPlugin( name='cisco_meraki_org_cellular_uplinks', - service_name='Cellular Uplink %s', + service_name='Uplink %s', discovery_function=discover_cellular_uplinks, check_function=check_cellular_uplinks, check_default_parameters={}, diff --git a/source/agent_based/cisco_meraki_org_device_uplinks.py b/source/cmk_addons_plugins/meraki/agent_based/device_uplinks.py similarity index 77% rename from source/agent_based/cisco_meraki_org_device_uplinks.py rename to source/cmk_addons_plugins/meraki/agent_based/device_uplinks.py index 47b181cee73f8f94afbed5b40a02c2440d67370b..26ccfc8e565bfee098c0d1545ed38dc60f620d25 100644 --- a/source/agent_based/cisco_meraki_org_device_uplinks.py +++ b/source/cmk_addons_plugins/meraki/agent_based/device_uplinks.py @@ -11,12 +11,21 @@ # inventory of cisco Meraki uplinks # 2024-04-27: made data parsing more robust +# 2024-06-29: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_org_device_uplinks.py in to device_uplinks.py + from collections.abc import Sequence -from cmk.base.plugins.agent_based.agent_based_api.v1 import register, TableRow -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import InventoryResult, StringTable -from cmk.base.plugins.agent_based.utils.cisco_meraki import load_json +from cmk.agent_based.v2 import ( + AgentSection, + InventoryPlugin, + InventoryResult, + StringTable, + TableRow, +) + +from cmk_addons.plugins.meraki.lib.utils import load_json __uplinks = [ { @@ -40,7 +49,7 @@ def parse_device_uplinks(string_table: StringTable) -> Sequence | None: return loaded_json[0]['uplinks'] if (loaded_json := load_json(string_table)) else None -register.agent_section( +agent_section_cisco_meraki_org_device_uplinks_info = AgentSection( name="cisco_meraki_org_device_uplinks_info", parse_function=parse_device_uplinks, ) @@ -58,7 +67,9 @@ def inventory_device_uplinks(section: Sequence | None) -> InventoryResult: **({"address": str(address['address'])} if address.get('address') is not None else {}), } inventory_columns = { - **({"assignment_mode": str(address['assignmentMode'])} if address.get('assignmentMode') is not None else {}), + **({"assignment_mode": str( + address['assignmentMode'] + )} if address.get('assignmentMode') is not None else {}), **({"gateway": str(address['gateway'])} if address.get('gateway') is not None else {}), **({"public_address": str(address['public']['address'])} if address.get('public', {}).get( 'address') is not None else {}), @@ -70,7 +81,7 @@ def inventory_device_uplinks(section: Sequence | None) -> InventoryResult: ) -register.inventory_plugin( +inventory_plugin_cisco_meraki_org_device_uplinks_info = InventoryPlugin( name="cisco_meraki_org_device_uplinks_info", inventory_function=inventory_device_uplinks, ) diff --git a/source/agent_based/cisco_meraki_org_networks.py b/source/cmk_addons_plugins/meraki/agent_based/networks.py similarity index 87% rename from source/agent_based/cisco_meraki_org_networks.py rename to source/cmk_addons_plugins/meraki/agent_based/networks.py index 945396da4cb682805dc3c477729a7b7bc86ca7dc..cd98c3ca5154c629dbeb84676b135a24ac5b350d 100644 --- a/source/agent_based/cisco_meraki_org_networks.py +++ b/source/cmk_addons_plugins/meraki/agent_based/networks.py @@ -8,23 +8,22 @@ # Date : 2023-11-04 # File : cisco_meraki_org_networks.py (check plugin) -from dataclasses import dataclass +# 2024-06-29: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_org_networks.py in to networks.py + from collections.abc import Sequence +from dataclasses import dataclass from typing import Final -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - TableRow, - register, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( +from cmk.agent_based.v2 import ( + AgentSection, + InventoryPlugin, InventoryResult, StringTable, + TableRow, ) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - MerakiAPIData, - MerakiNetwork, # type: ignore[import] - load_json, -) +from cmk_addons.plugins.meraki.lib.utils import MerakiAPIData, MerakiNetwork, load_json + _API_NAME_ORGANISATION_NAME: Final = "name" @@ -50,7 +49,7 @@ class NetworkInfo(MerakiNetwork): url: str @classmethod - def parse(cls, organisations: MerakiAPIData) -> "NetworkInfo": + def parse(cls, organisations: MerakiAPIData): networks = [] for organisation in organisations: for network in organisation: @@ -74,7 +73,7 @@ def parse_meraki_networks(string_table: StringTable) -> Sequence[NetworkInfo] | return NetworkInfo.parse(loaded_json) if (loaded_json := load_json(string_table)) else None -register.agent_section( +agent_section_cisco_meraki_org_networks = AgentSection( name="cisco_meraki_org_networks", parse_function=parse_meraki_networks, ) @@ -105,7 +104,7 @@ def inventory_meraki_networks(section: Sequence[NetworkInfo] | None) -> Inventor ) -register.inventory_plugin( +inventory_plugin_cisco_meraki_org_networks = InventoryPlugin( name="cisco_meraki_org_networks", inventory_function=inventory_meraki_networks, ) diff --git a/source/agent_based/cisco_meraki_organisations_api.py b/source/cmk_addons_plugins/meraki/agent_based/organisations_api.py similarity index 84% rename from source/agent_based/cisco_meraki_organisations_api.py rename to source/cmk_addons_plugins/meraki/agent_based/organisations_api.py index 130754d79313da306e377948703aef34eef23fb1..dd0660105cbfa798512a1ca45c581b33bf2f969c 100644 --- a/source/agent_based/cisco_meraki_organisations_api.py +++ b/source/cmk_addons_plugins/meraki/agent_based/organisations_api.py @@ -11,28 +11,28 @@ # 2024-04-27: made data parsing more robust # 2024-05-12: added api request count # refactoring parse functions as class method +# 2024-06-29: refactoring for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_organisations_api.py in to organisations_api.py -from _collections_abc import Mapping, Sequence +from collections.abc import Mapping, Sequence from dataclasses import dataclass -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + InventoryPlugin, + InventoryResult, Result, Service, State, + StringTable, TableRow, check_levels, - register, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - CheckResult, - DiscoveryResult, - InventoryResult, - StringTable, -) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - get_int, # type: ignore[import] - load_json, ) +from cmk_addons.plugins.meraki.lib.utils import add_org_id_name_to_output, get_int, load_json + __orgaisations = [ {'id': '610473', @@ -122,7 +122,7 @@ def parse_meraki_organisations(string_table: StringTable) -> Mapping[str, Organi return {org['id']: Organisation.parse(org) for org in json_data} -register.agent_section( +agent_section_cisco_meraki_org_organisations = AgentSection( name='cisco_meraki_org_organisations', parsed_section_name='cisco_meraki_organisations_api', parse_function=parse_meraki_organisations, @@ -140,7 +140,7 @@ def parse_cisco_meraki_org_api_requests_by_organization( } -register.agent_section( +agent_section_cisco_meraki_org_api_requests_by_organization = AgentSection( name='cisco_meraki_org_api_requests_by_organization', parsed_section_name='cisco_meraki_org_api_requests_by_organization', parse_function=parse_cisco_meraki_org_api_requests_by_organization, @@ -164,7 +164,7 @@ def inventory_meraki_organisations(section: Mapping[str, Organisation]) -> Inven ) -register.inventory_plugin( +inventory_plugin_cisco_meraki_organisations_api = InventoryPlugin( name='cisco_meraki_organisations_api', # sections=['cisco_meraki_organisations'], inventory_function=inventory_meraki_organisations, @@ -202,32 +202,19 @@ def check_organisations_api( ) -> CheckResult: if (org := section_cisco_meraki_organisations_api.get(params.get('internal_item_name'))) is None: return + + yield from add_org_id_name_to_output( + org.id, + org.name, + params['item_variant'], + params.get('dont_show_alias_on_info'), + ) + yield Result( state=State.OK if org.api else State(params['state_api_not_enabled']), - summary=f'Status: {_api_status[org.api]}', + summary=f'({_api_status[org.api]})', + details=f'Status: {_api_status[org.api]}', ) - org_id = f'ID: {org.id}' - org_name = f'Name: {org.name}' - org_id_notice = f'Organisation ID: {org.id}' - org_name_notice = f'Organisation name: {org.name}' - - match params['item_variant']: - case 'org_id': - yield Result(state=State.OK, notice=org_id_notice) - if params.get('dont_show_alias_on_info'): - yield Result(state=State.OK, notice=org_name_notice) - else: - yield Result(state=State.OK, summary=org_name) - case 'org_name': - if params.get('dont_show_alias_on_info'): - yield Result(state=State.OK, notice=org_id_notice) - else: - yield Result(state=State.OK, summary=org_id) - yield Result(state=State.OK, notice=org_name_notice) - - case _: - yield Result(state=State.OK, notice=org_id_notice) - yield Result(state=State.OK, notice=org_name_notice) if section_cisco_meraki_org_api_requests_by_organization is None or ( api_requests := section_cisco_meraki_org_api_requests_by_organization.get(params.get( @@ -266,7 +253,7 @@ def check_organisations_api( ) -register.check_plugin( +check_plugin_cisco_meraki_organisations_api = CheckPlugin( name='cisco_meraki_organisations_api', sections=['cisco_meraki_organisations_api', 'cisco_meraki_org_api_requests_by_organization'], service_name='Cisco Meraki API %s', diff --git a/source/agent_based/cisco_meraki_switch_ports_statuses.py b/source/cmk_addons_plugins/meraki/agent_based/switch_ports_statuses.py similarity index 83% rename from source/agent_based/cisco_meraki_switch_ports_statuses.py rename to source/cmk_addons_plugins/meraki/agent_based/switch_ports_statuses.py index b9673ef5e47292a5355d8d04412df38e717e63a5..8409609a69d4a0c05c47338b0a52cdc90bb2ab44 100644 --- a/source/agent_based/cisco_meraki_switch_ports_statuses.py +++ b/source/cmk_addons_plugins/meraki/agent_based/switch_ports_statuses.py @@ -14,32 +14,34 @@ # added support for "realtime" traffic counters # refactoring parse functions as class method # 2024-05-20: added discovery rule for port status +# 2024-06-29: refactored for CMK 2.3 +# fixed render function for bandwidth -> uses now render.networkbandwidth +# try to match the output of a "normal" cmk interface service +# 2024-06-30: renamed from cisco_meraki_switch_ports_statuses.py in to switch_ports_statuses.py +# 2024-06-30: fixed discovery of (admin disabled) ports +# ToDo: create service label cmk/meraki/uplink:yes/no -from _collections_abc import Mapping, Sequence +from collections.abc import Mapping, Sequence from dataclasses import dataclass -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, + InventoryPlugin, + InventoryResult, Result, Service, State, + StringTable, TableRow, check_levels, - register, render, ) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - CheckResult, - DiscoveryResult, - InventoryResult, - StringTable, -) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - get_float, # type: ignore[import] - get_int, # type: ignore[import] - load_json, -) +from cmk_addons.plugins.meraki.lib.utils import get_float, get_int, load_json @dataclass(frozen=True) @@ -236,7 +238,7 @@ def parse_switch_ports_statuses(string_table: StringTable) -> Mapping[str, Switc return {port['portId']: SwitchPort.parse(port) for port in json_data} -register.agent_section( +agent_section_cisco_meraki_org_switch_ports_statuses = AgentSection( name="cisco_meraki_org_switch_ports_statuses", parse_function=parse_switch_ports_statuses, ) @@ -244,18 +246,31 @@ register.agent_section( def discover_switch_ports_statuses(params: Mapping[str, object], section: Mapping[str, SwitchPort]) -> DiscoveryResult: discovered_port_states = params['discovered_port_states'] - for port in section.keys(): - if section[port].enabled in discovered_port_states and section[port].status in discovered_port_states: + # adjust params, as we can not use True/False as keys anymore in rule sets :-( + if 'admin_enabled' in discovered_port_states: + discovered_port_states.append(True) + discovered_port_states.remove('admin_enabled') + if 'admin_disabled' in discovered_port_states: + discovered_port_states.append(False) + discovered_port_states.append('disabled') + discovered_port_states.remove('admin_disabled') + + for port in section.values(): + if port.enabled in discovered_port_states and port.status.lower() in discovered_port_states: yield Service( - item=port, + item=port.port_id, parameters={ - 'enabled': section[port].enabled, - 'status': section[port].status, - 'speed': section[port].speed, + 'enabled': port.enabled, + 'status': port.status, + 'speed': port.speed, } ) +def render_network_bandwidth_bits(value: int) -> str: + return render.networkbandwidth(value/8) + + def check_switch_ports_statuses(item: str, params: Mapping[str, any], section: Mapping[str, SwitchPort]) -> CheckResult: def _status_changed(is_state: str, was_state: str, state: int, message: str): if is_state != was_state: @@ -263,9 +278,7 @@ def check_switch_ports_statuses(item: str, params: Mapping[str, any], section: M was_state = was_state if was_state else 'N/A' yield Result(state=State(state), notice=f'{message}: from {was_state}, to {is_state}') - try: - port: SwitchPort = section[item] - except KeyError: + if (port := section.get(item)) is None: return # check admin state changed @@ -286,7 +299,7 @@ def check_switch_ports_statuses(item: str, params: Mapping[str, any], section: M state=params['state_op_change'] ) if port.status.lower() == 'connected': # check operational state - yield Result(state=State.OK, notice=f'Operational status: {port.status}') + yield Result(state=State.OK, summary=f'({port.status})', details=f'Operational status: {port.status}') # check speed changed yield from _status_changed( is_state=port.speed, @@ -296,34 +309,43 @@ def check_switch_ports_statuses(item: str, params: Mapping[str, any], section: M ) if params['speed'] == port.speed: # only if speed unchanged yield Result(state=State.OK, summary=f'Speed: {port.speed}') - if port.duplex.lower() == 'full': # check duplex state - yield Result(state=State.OK, summary=f'Duplex: {port.duplex}') - else: - yield Result(state=State(params['state_not_full_duplex']), summary=f'Duplex: {port.duplex}') - yield Result(state=State.OK, summary=f'Clients: {port.client_count}') if params.get('show_traffic'): - yield from check_levels( - value=port.traffic.sent, # Bits - label='Out', - metric_name='if_out_bps', - render_func=lambda v: render.networkbandwidth(v/8), # Bytes - # notice_only=True, - ) yield from check_levels( value=port.traffic.recv, # Bits label='In', metric_name='if_in_bps', - render_func=lambda v: render.networkbandwidth(v/8), # Bytes + render_func=render_network_bandwidth_bits, # Bytes + # notice_only=True, + ) + yield from check_levels( + value=port.traffic.sent, # Bits + label='Out', + metric_name='if_out_bps', + render_func=render_network_bandwidth_bits, # Bytes # notice_only=True, ) + + if port.duplex.lower() == 'full': # check duplex state + yield Result(state=State.OK, notice=f'Duplex: {port.duplex}') + else: + yield Result(state=State(params['state_not_full_duplex']), notice=f'Duplex: {port.duplex}') + yield Result(state=State.OK, notice=f'Clients: {port.client_count}') else: - yield Result(state=State(params['state_not_connected']), summary=f'Operational status: {port.status}') + yield Result( + state=State(params['state_not_connected']), + summary=f'({port.status})', + details=f'Operational status: {port.status}' + ) else: - yield Result(state=State(params['state_disabled']), summary=f'Admin status: {_admin_status[port.enabled]}') + yield Result( + state=State(params['state_disabled']), + summary=f'({_admin_status[port.enabled].title()})', + details=f'Admin status: {_admin_status[port.enabled].title()}', + ) if port.is_up_link: - yield Result(state=State.OK, summary=f'UP-Link: {_is_up_link[port.is_up_link]}') + yield Result(state=State.OK, summary='UP-Link', details=f'UP-Link: {_is_up_link[port.is_up_link]}') else: yield Result(state=State.OK, notice=f'UP-Link: {_is_up_link[port.is_up_link]}') @@ -344,7 +366,7 @@ def check_switch_ports_statuses(item: str, params: Mapping[str, any], section: M yield Result(state=State.UNKNOWN, summary=f'Secure Port enabled', details=f'Secure Port: {port.secure_port}') -register.check_plugin( +check_plugin_cisco_meraki_org_switch_ports_statuses = CheckPlugin( name='cisco_meraki_org_switch_ports_statuses', service_name='Port %s', discovery_function=discover_switch_ports_statuses, @@ -360,7 +382,7 @@ register.check_plugin( check_ruleset_name='cisco_meraki_switch_ports_statuses', discovery_ruleset_name='discovery_cisco_meraki_switch_ports_statuses', discovery_default_parameters={ - 'discovered_port_states': [True, False, 'Connected', 'Disconnected'] + 'discovered_port_states': ['admin_enabled', 'admin_disabled', 'connected', 'disconnected'] } ) @@ -388,7 +410,7 @@ def inventory_meraki_cdp_cache(section: Mapping[str, SwitchPort]) -> InventoryRe ) -register.inventory_plugin( +inventory_plugin_inv_meraki_cdp_cache = InventoryPlugin( name='inv_meraki_cdp_cache', sections=['cisco_meraki_org_switch_ports_statuses'], inventory_function=inventory_meraki_cdp_cache, @@ -419,7 +441,7 @@ def inventory_meraki_lldp_cache(section: Mapping[str, SwitchPort]) -> InventoryR ) -register.inventory_plugin( +inventory_plugin_inv_meraki_lldp_cache = InventoryPlugin( name='inv_meraki_lldp_cache', sections=['cisco_meraki_org_switch_ports_statuses'], inventory_function=inventory_meraki_lldp_cache, diff --git a/source/agent_based/cisco_meraki_org_wireless_device_status.py b/source/cmk_addons_plugins/meraki/agent_based/wireless_device_ssid_status.py similarity index 88% rename from source/agent_based/cisco_meraki_org_wireless_device_status.py rename to source/cmk_addons_plugins/meraki/agent_based/wireless_device_ssid_status.py index 754262834711302c1d5f9890dfe71407d6f2081b..16b03451be45fb0311b8923929265eb0a2eb974c 100644 --- a/source/agent_based/cisco_meraki_org_wireless_device_status.py +++ b/source/cmk_addons_plugins/meraki/agent_based/wireless_device_ssid_status.py @@ -11,27 +11,24 @@ # 2024-04-27: made data parsing more robust # 2024-06-23: fixed crash on empty json_data # moved data parsing in to SSID class +# 2024-06-30: renamed from cisco_meraki_org_wireless_device_status.py int to wireless_device_ssid_status.py -from _collections_abc import Mapping +from collections.abc import Mapping from dataclasses import dataclass -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, Metric, Result, Service, State, - register, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - CheckResult, - DiscoveryResult, StringTable, ) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - get_int, # type: ignore[import] - load_json, -) +from cmk_addons.plugins.meraki.lib.utils import get_int, load_json @dataclass(frozen=True) @@ -83,7 +80,7 @@ def parse_wireless_device_status(string_table: StringTable) -> Mapping[str, SSID return ssids -register.agent_section( +agent_section_cisco_meraki_org_wireless_device_status = AgentSection( name="cisco_meraki_org_wireless_device_status", parse_function=parse_wireless_device_status, ) @@ -104,10 +101,8 @@ def check_wireless_device_status( params: Mapping[str, any], section: Mapping[str, SSID] ) -> CheckResult: - try: - ssid: SSID = section[item] - except KeyError: - return None + if (ssid := section.get(item)) is None: + return yield Result(state=State.OK, summary=f'Name: {ssid.name}') if not ssid.enabled: @@ -123,12 +118,12 @@ def check_wireless_device_status( yield Result(state=State.OK, summary=f'Channel: {ssid.channel}') yield Metric(name='channel', value=ssid.channel) yield Result(state=State.OK, summary=f'Channel width: {ssid.channel_width}') - yield Metric(name='channel_width', value=int(ssid.channel_width.split(' ')[0]) * 1000000) # change to Hz from MHz + yield Metric(name='channel_width', value=int(ssid.channel_width.split(' ')[0]) * 1000000) # change MHz -> Hz yield Result(state=State.OK, summary=f'Power: {ssid.power}') yield Metric(name='signal_power', value=int(ssid.power.split(' ')[0])) -register.check_plugin( +check_plugin_cisco_meraki_org_wireless_device_status = CheckPlugin( name='cisco_meraki_org_wireless_device_status', service_name='SSID %s', discovery_function=discover_wireless_device_status, @@ -138,4 +133,3 @@ register.check_plugin( }, check_ruleset_name='cisco_meraki_wireless_device_status', ) - diff --git a/source/agent_based/cisco_meraki_org_wireless_ethernet_statuses.py b/source/cmk_addons_plugins/meraki/agent_based/wireless_ethernet_statuses.py similarity index 75% rename from source/agent_based/cisco_meraki_org_wireless_ethernet_statuses.py rename to source/cmk_addons_plugins/meraki/agent_based/wireless_ethernet_statuses.py index 046fd9a6a49a94f7cd7de066ad0a3d25047e62b5..9fc2fca6106790c47ce61a41cc03f5c988a2e5c0 100644 --- a/source/agent_based/cisco_meraki_org_wireless_ethernet_statuses.py +++ b/source/cmk_addons_plugins/meraki/agent_based/wireless_ethernet_statuses.py @@ -9,26 +9,27 @@ # File : cisco_meraki_org_wireless_ethernet_statuses.py (check plugin) # 2024-04-27: made data parsing more robust +# 2024-06-29: refactored for CMK 2.3 +# moved parse functions to class methods +# 2024-06-30: renamed from cisco_meraki_org_wireless_ethernet_statuses.py in to wireless_ethernet_statuses.py +# ToDo: create ruleset cisco_meraki_wireless_ethernet_statuses + +from collections.abc import Mapping from dataclasses import dataclass -from _collections_abc import Mapping -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + CheckResult, + DiscoveryResult, Result, Service, State, - register, - render, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - CheckResult, - DiscoveryResult, StringTable, + render, ) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - get_int, # type: ignore[import] - load_json, -) +from cmk_addons.plugins.meraki.lib.utils import get_int, load_json __ethernet_port_statuses = { 'aggregation': { @@ -70,6 +71,14 @@ class WirelessEthernetPortPower: ac: bool | None poe: bool | None + @classmethod + def parse(cls, power: Mapping[str, object]): + return cls( + mode=str(power['mode']) if power.get('mode') is not None else None, + ac=bool(power['ac']['isConnected']) if power.get('ac', {}).get('isConnected') is not None else None, + poe=bool(power['poe']['isConnected']) if power.get('poe', {}).get('isConnected') is not None else None, + ) + @dataclass(frozen=True) class WirelessEthernetPort: @@ -79,6 +88,19 @@ class WirelessEthernetPort: speed: int | None power: WirelessEthernetPortPower | None + @classmethod + def parse(cls, port: Mapping[str, object], power: WirelessEthernetPortPower): + return cls( + name=str(port['name']) if port.get('name') is not None else None, + power=power, + duplex=port['linkNegotiation']['duplex'] if port.get('linkNegotiation', {}).get( + 'duplex') is not None else None, + # changed to bit/s + speed=int(port['linkNegotiation']['speed']) * 125000 if get_int(port.get('linkNegotiation', {}).get( + 'speed')) else None, + poe=str(port['poe']['standard']) if port.get('poe', {}).get('standard') is not None else None, + ) + _is_connected = { True: 'Yes', @@ -90,29 +112,12 @@ def parse_wireless_ethernet_statuses(string_table: StringTable) -> Mapping[str, json_data = load_json(string_table) json_data = json_data[0] - power = WirelessEthernetPortPower( - mode=str(json_data['power']['mode']) if json_data['power'].get('mode') is not None else None, - ac=bool(json_data['power']['ac']['isConnected']) if json_data['power'].get('ac', {}).get( - 'isConnected') is not None else None, - poe=bool(json_data['power']['poe']['isConnected']) if json_data['power'].get('poe', {}).get( - 'isConnected') is not None else None, - ) if json_data.get('power') is not None else None + power = WirelessEthernetPortPower.parse(json_data['power']) if json_data.get('power') is not None else None - return { - port['name']: WirelessEthernetPort( - name=str(port['name']) if port.get('name') is not None else None, - power=power, - duplex=port['linkNegotiation']['duplex'] if port.get('linkNegotiation', {}).get( - 'duplex') is not None else None, - # changed to bit/s - speed=int(port['linkNegotiation']['speed']) * 125000 if get_int(port.get('linkNegotiation', {}).get( - 'speed')) else None, - poe=str(port['poe']['standard']) if port.get('poe', {}).get('standard') is not None else None, - ) for port in json_data.get('ports', []) - } + return {port['name']: WirelessEthernetPort.parse(port, power) for port in json_data.get('ports', [])} -register.agent_section( +agent_section_cisco_meraki_org_wireless_ethernet_statuses = AgentSection( name="cisco_meraki_org_wireless_ethernet_statuses", parse_function=parse_wireless_ethernet_statuses, ) @@ -132,9 +137,7 @@ def check_wireless_ethernet_statuses( if is_state != was_state: yield Result(state=State(state), notice=f'{message}: is {is_state}, was {was_state}') - try: - port: WirelessEthernetPort = section[item] - except KeyError: + if (port := section.get(item)) is None: return None if port.speed: @@ -171,7 +174,7 @@ def check_wireless_ethernet_statuses( yield Result(state=State.OK, summary=f'PoE standard: {port.poe}') -register.check_plugin( +check_plugin_cisco_meraki_org_wireless_ethernet_statuses = CheckPlugin( name='cisco_meraki_org_wireless_ethernet_statuses', service_name='Port %s', discovery_function=discover_wireless_ethernet_statuses, @@ -182,5 +185,5 @@ register.check_plugin( 'state_no_speed': 1, 'state_speed_change': 1, }, - check_ruleset_name='cisco_meraki_wireless_ethernet_statuses', + # check_ruleset_name='cisco_meraki_wireless_ethernet_statuses', ) diff --git a/source/cmk_addons_plugins/meraki/graphing/packages.py b/source/cmk_addons_plugins/meraki/graphing/packages.py new file mode 100644 index 0000000000000000000000000000000000000000..108400b33d1cbba8e673a387f1483698335e7b4e --- /dev/null +++ b/source/cmk_addons_plugins/meraki/graphing/packages.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2023-11-04 +# File : cisco_meraki.py (metrics) +# +# 2023-11-12: added wireless device status (channel, channel width, signal power) +# 2024-05-12: added switch port statuses and API return Codes +# 2024-06-24: fixed, wrong total for SSID perfometer signal_power -> 30 +# 2024-06-27: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki.py in to packages.py + +from cmk.graphing.v1 import Title, graphs, metrics, perfometers + +# +# unit definitions +# +UNIT_DBM = metrics.Unit(metrics.DecimalNotation('dBm')) +UNIT_HZ = metrics.Unit(metrics.SINotation('hZ')) +UNIT_NUMBER = metrics.Unit(metrics.DecimalNotation('')) +UNIT_PERCENT = metrics.Unit(metrics.DecimalNotation('%')) +UNIT_TIME = metrics.Unit(metrics.TimeNotation()) +# +# license overview +# +metric_sum_licensed_devices = metrics.Metric( + name='sum_licensed_devices', + title=Title('Licensed devices'), + unit=UNIT_NUMBER, + color=metrics.Color.LIGHT_GREEN, +) + +metric_remaining_time = metrics.Metric( + name='remaining_time', + title=Title('Remaining time'), + unit=UNIT_TIME, + color=metrics.Color.GREEN, +) + +graph_cisco_meraki_remaining_time = graphs.Graph( + name='cisco_meraki.remaining_time', + title=Title('Cisco Meraki Licenses remaining time'), + compound_lines=['remaining_time'], + simple_lines=[ + metrics.WarningOf("remaining_time"), + metrics.CriticalOf("remaining_time"), + ], + minimal_range=graphs.MinimalRange(0, 180), +) + +graph_cisco_meraki_licensed_devices = graphs.Graph( + name='cisco_meraki_licensed_devices', + title=Title('Cisco Meraki Licensed devices'), + compound_lines=['sum_licensed_devices'], + minimal_range=graphs.MinimalRange(0, 10), +) + +perfometer_licensing = perfometers.Stacked( + name="merak_licensing", + # upper and lower are in the wrong order + lower=perfometers.Perfometer( + name='sum_licensed_devices', + focus_range=perfometers.FocusRange(perfometers.Open(0), perfometers.Open(100)), + segments=['sum_licensed_devices'], + ), + upper=perfometers.Perfometer( + name='remaining_time', + focus_range=perfometers.FocusRange(perfometers.Open(0), perfometers.Open(180)), + segments=["remaining_time"], + ), +) + +# +# wireless devices status +# +metric_signal_power = metrics.Metric( + name='signal_power', + title=Title("Power"), + unit=UNIT_DBM, + color=metrics.Color.GREEN, +) + +metric_channel_width = metrics.Metric( + name="channel_width", + title=Title("Channel Width"), + unit=UNIT_HZ, + color=metrics.Color.BLUE, +) + +metric_channel = metrics.Metric( + name="channel", + title=Title("Channel"), + unit=UNIT_NUMBER, + color=metrics.Color.DARK_YELLOW, +) + +graph_cisco_meraki_wireless_device_status_signal_power = graphs.Graph( + name='cisco_meraki_wireless_device_status_signal_power', + title=Title('Signal power'), + compound_lines=['signal_power'], + minimal_range=graphs.MinimalRange(0, 'signal_power:max'), +) + +graph_cisco_meraki_wireless_device_status_channel_width = graphs.Graph( + name='cisco_meraki_wireless_device_status_channel_width', + title=Title('Channel Width'), + compound_lines=['channel_width'], + minimal_range=graphs.MinimalRange(0, 'channel_width:max'), +) + +graph_cisco_meraki_wireless_device_status_channel = graphs.Graph( + name='cisco_meraki_wireless_device_status_channel', + title=Title('Channel'), + compound_lines=['channel'], + minimal_range=graphs.MinimalRange(0, 'channel:max'), +) + +perfometer_signal_power = perfometers.Perfometer( + name='signal_power', + segments=['signal_power'], + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Closed(40)) +) + +# +# API return Codes +# +metric_api_code_2xx = metrics.Metric( + name="api_code_2xx", + title=Title("Code 2xx"), + unit=UNIT_NUMBER, + color=metrics.Color.LIGHT_GREEN, +) + +metric_api_code_3xx = metrics.Metric( + name="api_code_3xx", + title=Title("Code 3xx"), + unit=UNIT_NUMBER, + color=metrics.Color.LIGHT_BLUE, +) + +metric_api_code_4xx = metrics.Metric( + name="api_code_4xx", + title=Title("Code 4xx"), + unit=UNIT_NUMBER, + color=metrics.Color.LIGHT_RED, +) + +metric_api_code_5xx = metrics.Metric( + name="api_code_5xx", + title=Title("Code 5xx"), + unit=UNIT_NUMBER, + color=metrics.Color.DARK_RED, +) + +graph_cisco_meraki_cisco_meraki_organisations_api_code = graphs.Bidirectional( + name='cisco_meraki_cisco_meraki_organisations_api_code', + title=Title('Cisco Meraki API response codes'), + upper=graphs.Graph( + name='api_code_ok', + title=Title('Cisco Meraki API response codes'), + simple_lines=[ + 'api_code_2xx', + 'api_code_3xx', + ], + optional=[ + 'api_code_2xx', + 'api_code_3xx', + ] + ), + lower=graphs.Graph( + name='api_codebad', + title=Title('Cisco Meraki API response codes'), + simple_lines=[ + 'api_code_4xx', + 'api_code_5xx', + ], + optional=[ + 'api_code_4xx', + 'api_code_5xx', + ] + ), +) + +perfometer_api_code = perfometers.Stacked( + name='api_code', + lower=perfometers.Perfometer( + name='api_code_2xx', + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(30)), + segments=["api_code_2xx"], + ), + upper=perfometers.Perfometer( + name='api_code_4xx', + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(30)), + segments=["api_code_4xx"], + ) +) + +perfometer_api_code_2xx = perfometers.Perfometer( + name='api_code_2xx', + segments=["api_code_2xx"], + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(50)), +) + +perfometer_api_code_4xx = perfometers.Perfometer( + name='api_code_4xx', + segments=['api_code_4xx'], + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(50)), +) + +# +# appliance performance/utilization +# +metric_utilization = metrics.Metric( + name="utilization", + title=Title("Utilization"), + unit=UNIT_PERCENT, + color=metrics.Color.LIGHT_GREEN, +) + +graph_cisco_meraki_cisco_meraki_appliance_utilization = graphs.Graph( + name='cisco_meraki_cisco_meraki_appliance_utilization', + title=Title('Cisco Meraki Appliance Utilization'), + compound_lines=['utilization'], + simple_lines=[ + metrics.WarningOf("utilization"), + metrics.CriticalOf("utilization"), + ], + minimal_range=graphs.MinimalRange(0, 100), +) + +perfometer_utilization = perfometers.Perfometer( + name='utilization', + segments=['utilization'], + focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Closed(100)), +) diff --git a/source/lib/python3/cmk/special_agents/agent_cisco_meraki.py b/source/cmk_addons_plugins/meraki/lib/agent.py similarity index 95% rename from source/lib/python3/cmk/special_agents/agent_cisco_meraki.py rename to source/cmk_addons_plugins/meraki/lib/agent.py index e1bd3fcbd0e29683dc1317547e6e99e003439519..860cc46d65c7acac1a41802e04d97410dd8efb0e 100644 --- a/source/lib/python3/cmk/special_agents/agent_cisco_meraki.py +++ b/source/cmk_addons_plugins/meraki/lib/agent.py @@ -37,8 +37,6 @@ # 2024-05-20: made appliance uplinks usage user selectable # made API requests per org user selectable # 2024-06-23: added cache time per section -> not nice but should work. -# 2024-06-24: renamed cache time per section option form --cache_per_section to --cache-per-section -# fixed --cache-per-section parameter evaluation # ToDo: create inventory from Networks, is per organisation, not sure where/how to put this in the inventory # ToDo: list Connected Datacenters like Umbrella https://developer.cisco.com/meraki/api-v1/list-data-centers/ @@ -64,45 +62,66 @@ from enum import auto, Enum from logging import getLogger from os import environ from pathlib import Path -from random import randrange +# from random import randrange from requests import request, RequestException from time import strftime, gmtime, time as now_time from time import time_ns -from typing import Final, TypedDict, Any +from typing import Final, TypedDict, Any, List # from urllib.request import getproxies import meraki # type: ignore[import] from cmk.utils.paths import tmp_dir -from cmk.special_agents.utils.agent_common import ( +from cmk.special_agents.v0_unstable.agent_common import ( ConditionalPiggybackSection, SectionWriter, special_agent_main, ) -from cmk.special_agents.utils.argument_parsing import create_default_argument_parser # , Args -from cmk.special_agents.utils.misc import DataCache - -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - MerakiNetwork, # type: ignore[import] - _SEC_NAME_APPLIANCE_UPLINKS, # type: ignore[import] - _SEC_NAME_APPLIANCE_UPLINKS_USAGE, # type: ignore[import] - _SEC_NAME_APPLIANCE_VPNS, # type: ignore[import] - _SEC_NAME_APPLIANCE_PERFORMANCE, # type: ignore[import] - _SEC_NAME_CELLULAR_UPLINKS, # type: ignore[import] - _SEC_NAME_DEVICE_INFO, # type: ignore[import] - _SEC_NAME_DEVICE_STATUSES, # type: ignore[import] - _SEC_NAME_DEVICE_UPLINKS_INFO, # type: ignore[import] - _SEC_NAME_LICENSES_OVERVIEW, # type: ignore[import] - _SEC_NAME_NETWORKS, # type: ignore[import] - _SEC_NAME_ORGANISATIONS, # type: ignore[import] - _SEC_NAME_ORG_API_REQUESTS, # type: ignore[import] - _SEC_NAME_SENSOR_READINGS, # type: ignore[import] - _SEC_NAME_SWITCH_PORTS_STATUSES, # type: ignore[import] - _SEC_NAME_WIRELESS_DEVICE_STATUS, # type: ignore[import] - _SEC_NAME_WIRELESS_ETHERNET_STATUSES, # type: ignore[import] +from cmk.special_agents.v0_unstable.argument_parsing import create_default_argument_parser # , Args +from cmk.special_agents.v0_unstable.misc import DataCache + +from cmk_addons.plugins.meraki.lib.utils import ( + MerakiNetwork, + + # parameter names + _SEC_NAME_APPLIANCE_UPLINKS, + _SEC_NAME_APPLIANCE_UPLINKS_USAGE, + _SEC_NAME_APPLIANCE_VPNS, + _SEC_NAME_APPLIANCE_PERFORMANCE, + _SEC_NAME_CELLULAR_UPLINKS, + _SEC_NAME_DEVICE_INFO, + _SEC_NAME_DEVICE_STATUSES, + _SEC_NAME_DEVICE_UPLINKS_INFO, + _SEC_NAME_LICENSES_OVERVIEW, + _SEC_NAME_NETWORKS, + _SEC_NAME_ORGANISATIONS, + _SEC_NAME_ORG_API_REQUESTS, + _SEC_NAME_SENSOR_READINGS, + _SEC_NAME_SWITCH_PORTS_STATUSES, + _SEC_NAME_WIRELESS_DEVICE_STATUS, + _SEC_NAME_WIRELESS_ETHERNET_STATUSES, # Early Access - _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, # type: ignore[import] + _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, + + # api cache defaults per section + _SEC_CACHE_APPLIANCE_PERFORMANCE, + _SEC_CACHE_APPLIANCE_UPLINKS_USAGE, + _SEC_CACHE_APPLIANCE_UPLINKS, + _SEC_CACHE_APPLIANCE_VPNS, + _SEC_CACHE_CELLULAR_UPLINKS, + _SEC_CACHE_DEVICE_INFO, + _SEC_CACHE_DEVICE_STATUSES, + _SEC_CACHE_DEVICE_UPLINKS_INFO, + _SEC_CACHE_LICENSES_OVERVIEW, + _SEC_CACHE_NETWORKS, + _SEC_CACHE_ORG_API_REQUESTS, + _SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, + _SEC_CACHE_ORGANISATIONS, + _SEC_CACHE_SENSOR_READINGS, + _SEC_CACHE_SWITCH_PORTS_STATUSES, + _SEC_CACHE_WIRELESS_DEVICE_STATUS, + _SEC_CACHE_WIRELESS_ETHERNET_STATUSES, ) _LOGGER = getLogger("agent_cisco_meraki") @@ -122,6 +141,7 @@ _API_NAME_NETWORK_ID: Final = 'networkId' _API_NAME_ORGANISATION_ID: Final = "id" _API_NAME_ORGANISATION_NAME: Final = "name" +# map section parameter name to python name (do we really need this, why not use the name ("-" -> "_")? _SECTION_NAME_MAP = { _SEC_NAME_APPLIANCE_UPLINKS: "appliance_uplinks", _SEC_NAME_APPLIANCE_UPLINKS_USAGE: "appliance_uplinks_usage", @@ -845,7 +865,6 @@ class MerakiOrganisation: piggyback=self._adjust_piggyback(host=piggyback), ) - if devices_by_type.get(_API_NAME_DEVICE_TYPE_SWITCH): if _SEC_NAME_SWITCH_PORTS_STATUSES not in self.config.excluded_sections: for switch in devices_by_type[_API_NAME_DEVICE_TYPE_SWITCH]: @@ -1102,7 +1121,7 @@ def parse_arguments(argv: Sequence[str] | None) -> Args: parser.add_argument( "--excluded-sections", - nargs="+", + nargs="*", choices=list(_SECTION_NAME_MAP), default=[], help="Sections that are excluded form data collected.", @@ -1144,8 +1163,26 @@ def parse_arguments(argv: Sequence[str] | None) -> Args: '--cache-per-section', nargs="+", type=int, - help="List of chache time per section in minutes", - default=[0, 0, 60, 60, 60, 60, 60, 60, 600, 600, 0, 0, 600, 0, 0, 30, 30] + help="List of cache time per section in minutes", + default=[ + _SEC_CACHE_APPLIANCE_PERFORMANCE, + _SEC_CACHE_APPLIANCE_UPLINKS_USAGE, + _SEC_CACHE_APPLIANCE_UPLINKS, + _SEC_CACHE_APPLIANCE_VPNS, + _SEC_CACHE_CELLULAR_UPLINKS, + _SEC_CACHE_DEVICE_INFO, + _SEC_CACHE_DEVICE_STATUSES, + _SEC_CACHE_DEVICE_UPLINKS_INFO, + _SEC_CACHE_LICENSES_OVERVIEW, + _SEC_CACHE_NETWORKS, + _SEC_CACHE_ORG_API_REQUESTS, + _SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, + _SEC_CACHE_ORGANISATIONS, + _SEC_CACHE_SENSOR_READINGS, + _SEC_CACHE_SWITCH_PORTS_STATUSES, + _SEC_CACHE_WIRELESS_DEVICE_STATUS, + _SEC_CACHE_WIRELESS_ETHERNET_STATUSES + ] ) return parser.parse_args(argv) diff --git a/source/agent_based/utils/cisco_meraki.py b/source/cmk_addons_plugins/meraki/lib/utils.py similarity index 65% rename from source/agent_based/utils/cisco_meraki.py rename to source/cmk_addons_plugins/meraki/lib/utils.py index 2ada91fd7af1419d431f4256556b634961e96d06..810af8679a6934857233ada91dab85ca4687af15 100644 --- a/source/agent_based/utils/cisco_meraki.py +++ b/source/cmk_addons_plugins/meraki/lib/utils.py @@ -8,6 +8,8 @@ # - changed check_last_reported_ts to output report as metric/levels # - added levels_upper check_last_reported_ts # - added section names from the cisco meraki special agent (for use in WATO) +# 2024-06-30: moved to cmk_addons/plugins/meraki/lib +# renamed from cisco_meraki.py to utils.py import json import time @@ -20,6 +22,7 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import CheckResul MerakiAPIData = Mapping[str, object] +# parameter names for agent options _SEC_NAME_ORGANISATIONS: Final = "_organisations" # internal use runs always _SEC_NAME_DEVICE_INFO: Final = "_device_info" # Not configurable, needed for piggyback _SEC_NAME_NETWORKS: Final = "_networks" # internal use, runs always, needed for network names @@ -30,7 +33,7 @@ _SEC_NAME_APPLIANCE_UPLINKS_USAGE: Final = "appliance-uplinks-usage" _SEC_NAME_APPLIANCE_VPNS: Final = "appliance-vpns" _SEC_NAME_APPLIANCE_PERFORMANCE: Final = "appliance-performance" _SEC_NAME_CELLULAR_UPLINKS: Final = "cellular-uplinks" -_SEC_NAME_DEVICE_STATUSES: Final = "device-statuses" +_SEC_NAME_DEVICE_STATUSES: Final = "device-status" _SEC_NAME_DEVICE_UPLINKS_INFO: Final = "device-uplinks-info" _SEC_NAME_LICENSES_OVERVIEW: Final = "licenses-overview" _SEC_NAME_SENSOR_READINGS: Final = "sensor-readings" @@ -39,6 +42,27 @@ _SEC_NAME_WIRELESS_DEVICE_STATUS: Final = "wireless-device-status" _SEC_NAME_WIRELESS_ETHERNET_STATUSES: Final = "wireless-ethernet-statuses" +# api cache defaults per section +_SEC_CACHE_APPLIANCE_PERFORMANCE = 0 +_SEC_CACHE_APPLIANCE_UPLINKS_USAGE = 0 +_SEC_CACHE_APPLIANCE_UPLINKS = 60 +_SEC_CACHE_APPLIANCE_VPNS = 60 +_SEC_CACHE_CELLULAR_UPLINKS = 60 +_SEC_CACHE_DEVICE_INFO = 60 +_SEC_CACHE_DEVICE_STATUSES = 60 +_SEC_CACHE_DEVICE_UPLINKS_INFO = 60 +_SEC_CACHE_LICENSES_OVERVIEW = 600 +_SEC_CACHE_NETWORKS = 600 +_SEC_CACHE_ORG_API_REQUESTS = 0 +_SEC_CACHE_ORG_SWITCH_PORTS_STATUSES = 0 +_SEC_CACHE_ORGANISATIONS = 600 +_SEC_CACHE_SENSOR_READINGS = 0 +_SEC_CACHE_SWITCH_PORTS_STATUSES = 0 +_SEC_CACHE_WIRELESS_DEVICE_STATUS = 30 +_SEC_CACHE_WIRELESS_ETHERNET_STATUSES = 30 + + + # Early Access _SEC_NAME_ORG_SWITCH_PORTS_STATUSES: Final = "org-switch-ports-statuses" @@ -100,3 +124,33 @@ def get_float(value: str | None) -> float | None: return float(value) except TypeError: return + + +def add_org_id_name_to_output( + organisation_id: str, + organisation_name: str, + item_variant: str, + dont_show_alias_on_info: bool, +) -> GeneratorExit: + org_id = f'[{organisation_id}]' + org_name = f'[{organisation_name}]' + org_id_notice = f'Organisation ID: {organisation_id}' + org_name_notice = f'Organisation name: {organisation_name}' + + match item_variant: + case 'org_id': + yield Result(state=State.OK, notice=org_id_notice) + if dont_show_alias_on_info: + yield Result(state=State.OK, notice=org_name_notice) + else: + yield Result(state=State.OK, summary=org_name, details=org_name_notice) + case 'org_name': + if dont_show_alias_on_info: + yield Result(state=State.OK, notice=org_id_notice) + else: + yield Result(state=State.OK, summary=org_id, details=org_id_notice) + yield Result(state=State.OK, notice=org_name_notice) + + case _: + yield Result(state=State.OK, notice=org_id_notice) + yield Result(state=State.OK, notice=org_name_notice) \ No newline at end of file diff --git a/source/cmk_addons_plugins/meraki/rulesets/appliance_performance.py b/source/cmk_addons_plugins/meraki/rulesets/appliance_performance.py new file mode 100644 index 0000000000000000000000000000000000000000..5424f4fcb902ec81c3a254c21066d9017d54a038 --- /dev/null +++ b/source/cmk_addons_plugins/meraki/rulesets/appliance_performance.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-06-23 +# File : cisco_meraki_org_appliance_performance.py (WATO) + +# 2024-06-27: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_org_appliance_performance.py in to appliance_performance.py +from cmk.rulesets.v1 import Help, Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + Integer, + LevelDirection, + SimpleLevels, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, HostCondition, Topic + + +def _parameter_form() -> Dictionary: + return Dictionary( + elements={ + 'levels_upper': DictElement( + parameter_form=SimpleLevels( + title=Title("Utilization"), + level_direction=LevelDirection.UPPER, + form_spec_template=Integer(), + prefill_fixed_levels=DefaultValue(value=(60, 80)), + help_text=Help( + 'The device utilization data reported to the Meraki' + ' dashboard is based on a load average measured over a' + ' period of one minute. The load value is returned in' + ' numeric values ranging from 1 through 100. A lower' + ' value indicates a lower load, and a higher value' + ' indicates a more intense workload. Currently, the' + ' device utilization value is calculated based upon the' + ' CPU utilization of the MX as well as its traffic load.' + ' If an MX device is consistently over 50% utilization' + ' during normal operation, upgrading to a higher' + ' throughput model or reducing the per-device load' + ' through horizontal scaling should be considered. For' + ' more information see:' + ' https://documentation.meraki.com-MX-Monitoring?and?' + 'Reporting-Device?Utiliyation' + ), + ) + ) + }, + ) + + +rule_spec_cisco_meraki_org_appliance_performance = CheckParameters( + name="cisco_meraki_org_appliance_performance", + topic=Topic.NETWORKING, + parameter_form=_parameter_form, + title=Title("Cisco Meraki Appliance Utilization"), + condition=HostCondition(), +) diff --git a/source/cmk_addons_plugins/meraki/rulesets/appliance_uplinks.py b/source/cmk_addons_plugins/meraki/rulesets/appliance_uplinks.py new file mode 100644 index 0000000000000000000000000000000000000000..6129d379fbaae39d8043aa5c7a034714cbd2ef90 --- /dev/null +++ b/source/cmk_addons_plugins/meraki/rulesets/appliance_uplinks.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2023-11-05 +# File : cisco_meraki_org_appliance_uplinks.py (WATO) + +# 2024-06-29: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_org_appliance_uplinks.py in to appliance_uplinks.py + +from cmk.rulesets.v1 import Label, Title, Help +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + FixedValue, + ServiceState, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic + + +def _parameter_form() -> Dictionary: + return Dictionary( + elements={ + 'status_map': DictElement( + parameter_form=Dictionary( + title=Title('Map uplink status to monitoring state'), + elements={ + "active": DictElement( + parameter_form=ServiceState( + title=Title('Uplink status "active"'), + prefill=DefaultValue(ServiceState.OK) + )), + "ready": DictElement( + parameter_form=ServiceState( + title=Title('Uplink status "ready"'), + prefill=DefaultValue(ServiceState.WARN), + )), + "not_connected": DictElement( + parameter_form=ServiceState( + title=Title('Uplink status "not connected"'), + prefill=DefaultValue(ServiceState.CRIT), + )), + "failed": DictElement( + parameter_form=ServiceState( + title=Title('Uplink status "failed"'), + prefill=DefaultValue(ServiceState.CRIT), + )), + }, + )), + 'show_traffic': DictElement( + parameter_form=FixedValue( + title=Title('Show bandwidth (use only with cache disabled)'), + help_text=Help( + 'Use only with cache disabled in the Meraki special agent settings. ' + 'The throughput is based on the usage for the last 60 seconds.' + ), + value=True, + label=Label("Bandwidth monitoring enabled") + )) + }, + ) + + +rule_spec_cisco_meraki_org_appliance_uplinks = CheckParameters( + name="cisco_meraki_org_appliance_uplinks", + topic=Topic.NETWORKING, + parameter_form=_parameter_form, + title=Title("Cisco Meraki Appliance uplinks"), + condition=HostAndItemCondition(item_title=Title('Uplink name')), +) diff --git a/source/cmk_addons_plugins/meraki/rulesets/appliance_vpns.py b/source/cmk_addons_plugins/meraki/rulesets/appliance_vpns.py new file mode 100644 index 0000000000000000000000000000000000000000..3cc8cca7cce1775aff0f9170839f4def907c3d47 --- /dev/null +++ b/source/cmk_addons_plugins/meraki/rulesets/appliance_vpns.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2023-11-05 +# File : cisco_meraki_org_appliance_vpns.py (WATO) + +# 2024-06-29: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_org_appliance_vpns.py in to appliance_vpns.py + +from cmk.rulesets.v1 import Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + ServiceState, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic + + +def _parameter_form() -> Dictionary: + return Dictionary( + elements={ + 'status_not_reachable': DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if the VPN peer is not reachable'), + prefill=DefaultValue(ServiceState.WARN), + )), + } + ) + + +rule_spec_cisco_meraki_org_appliance_vpns = CheckParameters( + name="cisco_meraki_org_appliance_vpns", + topic=Topic.NETWORKING, + parameter_form=_parameter_form, + title=Title("Cisco Meraki Appliance VPNs"), + condition=HostAndItemCondition(item_title=Title('VPN peer')), +) diff --git a/source/cmk_addons_plugins/meraki/rulesets/licenses_overviewi.py b/source/cmk_addons_plugins/meraki/rulesets/licenses_overviewi.py new file mode 100644 index 0000000000000000000000000000000000000000..cd207a6d3e175c816a5162c6627f91ecf0ef78d9 --- /dev/null +++ b/source/cmk_addons_plugins/meraki/rulesets/licenses_overviewi.py @@ -0,0 +1,100 @@ +#!/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. + +# enhancements by thl-cmk[at]outlook[dot]com, https://thl-cmk.hopto.org +# - changed remaining time (WATO) from Age (Days, Hours, Minutes, Seconds) to Days only +# - added WATO option for License state is not ok -> default to WARN +# - added discovery rule for ITEM variant (Org Name/Org ID - this is the default, Org Name, Org ID) + +# 2023-11-18: moved discovery rule set to cisco_meraki_organisations for reuse with cisco_meraki_organisations_api +# 2024-06-28: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_org_licenses_overviewi.py in to licenses_overviewi.py + +from cmk.rulesets.v1 import Label, Title, Help +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + FixedValue, + ServiceState, + SimpleLevels, + LevelDirection, + TimeSpan, + TimeMagnitude, + validators, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic + +_DAY = 60.0 * 60.0 * 24.0 + + +def _migrate(p: str | dict) -> dict[str, any]: + # change age to days, we suspect there is no warning less than 1 hour in seconds, + # and we will not warn/crit above 3600 days (10 years) + if p.get('remaining_expiration_time'): + warn, crit = p.get('remaining_expiration_time') + if warn >= 3600: + warn = int(warn / 86400) + if crit >= 3600: + crit = int(crit / 86400) + p['remaining_expiration_time'] = (warn, crit) + return p + + +def _parameter_form() -> Dictionary: + return Dictionary( + elements={ + "remaining_expiration_time": DictElement( + parameter_form=SimpleLevels[float]( + title=Title("Remaining licenses expiration time"), + help_text=Help(""), + form_spec_template=TimeSpan( + displayed_magnitudes=[TimeMagnitude.DAY], + custom_validate=(validators.NumberInRange(min_value=0),), + ), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue((40 * _DAY, 20 * _DAY)), + )), + # SimpleLevels[int]( + # title=Title("Lower levels for remaining expiration time of licenses"), + # level_direction=LevelDirection.LOWER, + # # elements=[ + # # Integer(title=_("Warning at"), unit='Days'), + # # Integer(title=_("Critical at"), unit='Days'), + # # ], + # )), + "state_license_not_ok": DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if License state is not OK'), + prefill=DefaultValue(ServiceState.WARN), + )), + 'dont_show_alias_on_info': DictElement( + parameter_form=FixedValue( + value=True, + title=Title('Don\'t show alias on info line'), + label=Label(''), + # help_test=Help( + # 'The alias is the Organisation ID or the Organisation name, depending on the Item.\n' + # 'If the item is the Organisation ID, the alias is the Organisation name and vice versa.\n' + # 'Organisation ID and Organisation name will always shown in the service details.' + # ) + )) + }, + # migrate=_migrate, + # ignored_keys=[ + # 'internal_item_name', + # 'old_item_name', + # 'item_variant', + # ], + ) + + +rule_spec_cisco_meraki_org_licenses_overview = CheckParameters( + name="cisco_meraki_org_licenses_overview", + topic=Topic.NETWORKING, + parameter_form=_parameter_form, + title=Title("Cisco Meraki Organisation Licenses Overview"), + condition=HostAndItemCondition(item_title=Title('Organization')), +) diff --git a/source/cmk_addons_plugins/meraki/rulesets/organisations.py b/source/cmk_addons_plugins/meraki/rulesets/organisations.py new file mode 100644 index 0000000000000000000000000000000000000000..1161b454f2b6cd21e6faf3bed9b556efbb3ef003 --- /dev/null +++ b/source/cmk_addons_plugins/meraki/rulesets/organisations.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2023-11-11 +# File : cisco_meraki_organisations.py (wato plugin) + +# 2023-11-18: split from cisco_meraki_org_licenses_overview.py +# 2024-06-29: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_organisations.py in to organisations.py + +from cmk.rulesets.v1 import Help, Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + SingleChoice, + SingleChoiceElement, +) +from cmk.rulesets.v1.rule_specs import DiscoveryParameters, HostCondition, Topic + + +def _discovery_form() -> Dictionary: + return Dictionary( + elements={ + 'item_variant': DictElement( + parameter_form=SingleChoice( + title=Title('Information to use as item'), + help_text=Help( + 'You can select how to build the item for this service. By default the Organization ID/name\n' + 'is used to stay compatible with the build in check. The information not used for the item\n' + 'will be added to the service output.' + ), + elements=[ + SingleChoiceElement( + name='org_id', + title=Title('Organization ID') + ), + SingleChoiceElement( + name='org_name', + title=Title('Organization name') + ), + SingleChoiceElement( + name='org_id_name', + title=Title('Organization ID/name') + ), + ], + prefill=DefaultValue('org_id_name') + )) + } + ) + + +rule_spec_discovery_meraki_organisations = DiscoveryParameters( + name="discovery_meraki_organisations", + topic=Topic.GENERAL, + parameter_form=_discovery_form, + title=Title("Cisco Meraki Organisation (API/Licenses)"), +) diff --git a/source/cmk_addons_plugins/meraki/rulesets/organisations_api.py b/source/cmk_addons_plugins/meraki/rulesets/organisations_api.py new file mode 100644 index 0000000000000000000000000000000000000000..e76cc02d360b1e86fe22f66b1f41342af727644e --- /dev/null +++ b/source/cmk_addons_plugins/meraki/rulesets/organisations_api.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2023-11-18 +# File : cisco_meraki_organisations_api.py (wato plugin) + +# 2024-06-29: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_organisations_api.py in to organisations_api.py +# moved ruleset from "Networking" to "Applications, Processes & Services" + +from cmk.rulesets.v1 import Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + ServiceState, + String, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic + + +def _parameter_form() -> Dictionary: + return Dictionary( + elements={ + "state_api_not_enabled": DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if API is not enabled'), + prefill=DefaultValue(ServiceState.WARN), + )), + + # params from discovery + 'internal_item_name': DictElement( + render_only=True, + parameter_form=ServiceState( + title=Title('Discovery internal item name') + )), + 'item_variant': DictElement( + render_only=True, + parameter_form=ServiceState( + title=Title('Discovery item variant') + )) + }, + # ignored_keys=[ + # 'internal_item_name', + # 'old_item_name', + # 'item_variant', + # ], + ) + + +rule_spec_cisco_meraki_organisations_api = CheckParameters( + name="cisco_meraki_organisations_api", + topic=Topic.APPLICATIONS, + parameter_form=_parameter_form, + title=Title("Cisco Meraki Organisation API"), + condition=HostAndItemCondition(item_title=Title('Organization')), +) + diff --git a/source/cmk_addons_plugins/meraki/rulesets/switch_ports_statuses.py b/source/cmk_addons_plugins/meraki/rulesets/switch_ports_statuses.py new file mode 100644 index 0000000000000000000000000000000000000000..3cea6f758399ab1aae5ec9efe18d8f6ed2f252f3 --- /dev/null +++ b/source/cmk_addons_plugins/meraki/rulesets/switch_ports_statuses.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-02-02 +# File : cisco_meraki_switch_ports_statuses.py (WATO) + +# 2024-05-12: added support for MerakiGetOrganizationSwitchPortsStatusesBySwitch (Early Access) +# added traffic counters as perfdata +# 2024-05-19: reworked switch port traffic +# 2024-05-20: added discovery rule for port status +# 2024-06-27: refactored for CMK 2.3 +# 2024-06-30: renamed from cisco_meraki_switch_ports_statuses.py in to switch_ports_statuses.py +# added params from discovery as render only + +from cmk.rulesets.v1 import Label, Title, Help +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + FixedValue, + ServiceState, + MultipleChoice, + MultipleChoiceElement, + String, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, DiscoveryParameters, HostAndItemCondition, Topic + +from cmk_addons.plugins.meraki.lib.utils import ( + _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, + _SEC_NAME_SWITCH_PORTS_STATUSES, +) + + +def _parameter_form(): + return Dictionary( + elements={ + 'state_disabled': DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if port is "disabled"'), + prefill=DefaultValue(ServiceState.OK), + )), + 'state_not_connected': DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if port is "not connected"'), + prefill=DefaultValue(ServiceState.OK), + )), + 'state_not_full_duplex': DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if port is "not full duplex"'), + prefill=DefaultValue(ServiceState.WARN), + )), + 'state_speed_change': DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if speed is changed'), + prefill=DefaultValue(ServiceState.WARN), + )), + 'state_admin_change': DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if admin state is changed'), + prefill=DefaultValue(ServiceState.WARN), + )), + 'state_op_change': DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if operational state is changed'), + prefill=DefaultValue(ServiceState.WARN), + )), + 'show_traffic': DictElement( + parameter_form=FixedValue( + value=True, + title=Title('Show bandwidth (use only with cache disabled)'), + label=Label('Bandwidth monitoring enabled'), + help_text=Help( + 'Use only with cache disabled in the Meraki special agent settings. ' + 'Depending on your Meraki organization size (in terms of number of switches) ' + 'this will exceeds the limits of the allowed API requests per second. You can try to ' + 'enable "Early Access" in the Meraki dashboard. In the Meraki special agent settings ' + f'switch from "{_SEC_NAME_SWITCH_PORTS_STATUSES}" to "{_SEC_NAME_ORG_SWITCH_PORTS_STATUSES}". ' + 'This will fetch all the switch data with one API request instead of one request for each ' + 'switch.' + ), + )), + # params from discovery + 'enabled': DictElement( + render_only=True, + parameter_form=String( + title=Title('Discovered admin state') + )), + 'status': DictElement( + render_only=True, + parameter_form=String( + title=Title('Discovered status') + ) + ), + 'speed': DictElement( + render_only=True, + parameter_form=String( + title=Title('Discovered speed') + ) + ) + }, + ) + + +rule_spec_cisco_meraki_switch_ports_statuses = CheckParameters( + name="cisco_meraki_switch_ports_statuses", + topic=Topic.NETWORKING, + parameter_form=_parameter_form, + title=Title("Cisco Meraki Switch Ports"), + condition=HostAndItemCondition(item_title=Title('Port ID')), +) + + +def _discovery_form(): + return Dictionary( + elements={ + 'discovered_port_states': DictElement( + parameter_form=MultipleChoice( + title=Title('Select Ports to discover'), + elements=[ + MultipleChoiceElement( + title=Title('Admin enabled'), + name='admin_enabled', + ), + MultipleChoiceElement( + title=Title('Admin disabled'), + name='admin_disabled', + ), + MultipleChoiceElement( + title=Title('Connected'), + name='connected', + ), + MultipleChoiceElement( + title=Title('Disconnected'), + name='disconnected', + ), + ], + help_text=Help('Select the port states for discovery'), + prefill=DefaultValue([ + 'admin_enabled', + 'admin_disabled', + 'connected', + 'disconnected', + ]) + )), + }, + ) + + +rule_spec_cisco_meraki_switch_ports_statuses_discovery = DiscoveryParameters( + name="discovery_cisco_meraki_switch_ports_statuses", + topic=Topic.GENERAL, + parameter_form=_discovery_form, + title=Title("Cisco Meraki Switch Ports"), +) diff --git a/source/cmk_addons_plugins/meraki/rulesets/wireless_device_ssid_status.py b/source/cmk_addons_plugins/meraki/rulesets/wireless_device_ssid_status.py new file mode 100644 index 0000000000000000000000000000000000000000..8f0d1705719098e80c4bc877e395c3f5f4e59cea --- /dev/null +++ b/source/cmk_addons_plugins/meraki/rulesets/wireless_device_ssid_status.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-02-02 +# File : cisco_meraki_org_wireless_status.py (WATO) + +# 2024-06-29: refactored for CMK 2.3 +# 2024-06-30 renamed from cisco_meraki_org_wireless_device_status.py in to wireless_device_ssid_status.py +# added SSI to the title + +from cmk.rulesets.v1 import Title +from cmk.rulesets.v1.form_specs import ( + DefaultValue, + DictElement, + Dictionary, + ServiceState, +) +from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic + + +def _parameter_form() -> Dictionary: + return Dictionary( + elements={ + 'state_if_not_enabled': DictElement( + parameter_form=ServiceState( + title=Title('Monitoring state if SSID is "not enabled"'), + prefill=DefaultValue(ServiceState.WARN), + )), + }, + ) + + +rule_spec_cisco_meraki_wireless_device_status = CheckParameters( + name="cisco_meraki_wireless_device_status", + topic=Topic.NETWORKING, + parameter_form=_parameter_form, + title=Title("Cisco Meraki Wireless device SSID"), + condition=HostAndItemCondition(item_title=Title("SSID")), +) diff --git a/source/cmk_plugins/cisco/rulesets/meraki.py b/source/cmk_plugins/cisco/rulesets/meraki.py new file mode 100644 index 0000000000000000000000000000000000000000..95fc8b3f2d3961322e5c7e9aec2112b3f0df01d4 --- /dev/null +++ b/source/cmk_plugins/cisco/rulesets/meraki.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# Copyright (C) 2022 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. + +# +# file path must be exactly as the file it shadows :-( +# local/lip/python3/cmk/plugins/cisco/rulesets + + +from collections.abc import Iterable, Sequence + +from cmk.rulesets.v1 import Help, Label, Message, Title +from cmk.rulesets.v1.form_specs import ( + # BooleanChoice, + DefaultValue, + DictElement, + Dictionary, + FixedValue, + Integer, + List, + MultipleChoice, + MultipleChoiceElement, + Password, + Proxy, + String, + migrate_to_password, + migrate_to_proxy, +) + +from cmk.rulesets.v1.form_specs.validators import ValidationError, NumberInRange +from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic +from cmk_addons.plugins.meraki.lib.utils import ( + _SEC_CACHE_APPLIANCE_PERFORMANCE, + _SEC_CACHE_APPLIANCE_UPLINKS_USAGE, + _SEC_CACHE_APPLIANCE_UPLINKS, + _SEC_CACHE_APPLIANCE_VPNS, + _SEC_CACHE_CELLULAR_UPLINKS, + _SEC_CACHE_DEVICE_INFO, + _SEC_CACHE_DEVICE_STATUSES, + _SEC_CACHE_DEVICE_UPLINKS_INFO, + _SEC_CACHE_LICENSES_OVERVIEW, + _SEC_CACHE_NETWORKS, + _SEC_CACHE_ORG_API_REQUESTS, + _SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, + _SEC_CACHE_ORGANISATIONS, + _SEC_CACHE_SENSOR_READINGS, + _SEC_CACHE_SWITCH_PORTS_STATUSES, + _SEC_CACHE_WIRELESS_DEVICE_STATUS, + _SEC_CACHE_WIRELESS_ETHERNET_STATUSES, +) + +_SEC_NAME_APPLIANCE_UPLINKS = "appliance_uplinks" +_SEC_NAME_APPLIANCE_UPLINKS_USAGE = "appliance_uplinks_usage" +_SEC_NAME_APPLIANCE_VPNS = "appliance_vpns" +_SEC_NAME_APPLIANCE_PERFORMANCE = "appliance_performance" +_SEC_NAME_CELLULAR_UPLINKS = "cellular_uplinks" +_SEC_NAME_DEVICE_INFO = "device_info" +_SEC_NAME_DEVICE_STATUSES = "device_status" +_SEC_NAME_DEVICE_UPLINKS_INFO = "device_uplinks_info" +_SEC_NAME_LICENSES_OVERVIEW = "licenses_overview" +_SEC_NAME_NETWORKS = "networks" +_SEC_NAME_ORGANISATIONS = "organisations" +_SEC_NAME_ORG_API_REQUESTS = "api_requests_by_organization" +_SEC_NAME_SENSOR_READINGS = "sensor_readings" +_SEC_NAME_SWITCH_PORTS_STATUSES = "switch_ports_statuses" +_SEC_NAME_WIRELESS_DEVICE_STATUS = "wireless_device_status" +_SEC_NAME_WIRELESS_ETHERNET_STATUSES = "wireless_ethernet_statuses" +_SEC_NAME_ORG_SWITCH_PORTS_STATUSES = "org_switch_ports_statuses" + +_SEC_TITLE_DEVICE_INFO = 'Device info (Organization)' +_SEC_TITLE_NETWORKS = 'Network info (Organization)' +_SEC_TITLE_ORGANISATIONS = 'Organization (Agent)' +_SEC_TITLE_ORG_API_REQUESTS = 'API request (Organizaion)' +_SEC_TITLE_APPLIANCE_UPLINKS = 'Appliances uplinks (Organizaion)' +_SEC_TITLE_APPLIANCE_UPLINKS_USAGE = 'Appliances uplinks usage (Organizaion)' +_SEC_TITLE_APPLIANCE_VPNS = 'Appliances VPNs (Organizaion)' +_SEC_TITLE_APPLIANCE_PERFORMANCE = 'Appliances Utilization (Device)' +_SEC_TITLE_CELLULAR_UPLINKS = 'Cellular devices uplinks (Organizaion)' +_SEC_TITLE_DEVICE_STATUSES = 'Devices status (Organizaion)' +_SEC_TITLE_DEVICE_UPLINKS_INFO = 'Devices uplink info (Organizaion)' +_SEC_TITLE_LICENSES_OVERVIEW = 'Licenses overview (Organizaion)' +_SEC_TITLE_SENSOR_READINGS = 'Sensors readings (Organizaion)' +_SEC_TITLE_SWITCH_PORTS_STATUSES = 'Switch ports status (Device)' +_SEC_TITLE_WIRELESS_ETHERNET_STATUSES = 'Wireless devices ethernet status (Organizaion)' +_SEC_TITLE_WIRELESS_DEVICE_STATUS = 'Wireless devices SSIDs status (Device)' +_SEC_TITLE_ORG_SWITCH_PORTS_STATUSES = 'Switch port status (Organizaion/Early Access)' + + +class DuplicateInList: # pylint: disable=too-few-public-methods + """ Custom validator that ensures the validated list has no duplicate entries. """ + + def __init__( + self, + ) -> None: + pass + + @staticmethod + def _get_default_errmsg(_duplicates: Sequence) -> Message: + return Message(f"Duplicate element in list. Duplicate elements: {', '.join(_duplicates)}") + + def __call__(self, value: List[str] | None) -> None: + if not isinstance(value, list): + return + _duplicates = [value[i] for i, x in enumerate(value) if value.count(x) > 1] + _duplicates = list(set(_duplicates)) + if _duplicates: + raise ValidationError(message=self._get_default_errmsg(_duplicates)) + + +def _migrate_to_valid_ident(value: object) -> Sequence[str]: + if not isinstance(value, Iterable): + raise ValueError("Invalid value {value} for sections") + + name_mapping = { + "licenses-overview": "licenses_overview", + "device-statuses": "device_statuses", + "sensor-readings": "sensor_readings", + } + + # return [name_mapping.get(s, s) for s in value] + return [s.replace('-', '_') for s in value] + + +def _form_special_agent_cisco_meraki() -> Dictionary: + return Dictionary( + title=Title("Cisco Meraki"), + elements={ + "api_key": DictElement( + parameter_form=Password( + title=Title("API Key"), + migrate=migrate_to_password + ), + required=True, + ), + "proxy": DictElement( + parameter_form=Proxy( + migrate=migrate_to_proxy + ) + ), + "no_cache": DictElement( + parameter_form=FixedValue( # BooleanChoice needs 2 clicks :-( + title=Title("Disable Cache"), + help_text=Help( + "Never use cached information. By default the agent will cache received " + "data to avoid API limits and speed up the data retrievel." + ), + label=Label("API Cache is disabled"), + value=True, + ) + ), + 'org_id_as_prefix': DictElement( + parameter_form=FixedValue( + value=True, + title=Title('Uese organisation ID as host prefix'), + label=Label("The Organization-id will be used as host name prefix"), + help_text=Help( + 'The organisation ID will be used as prefix for the hostname (separated by a "\'"). Use ' + 'this option together with a "Hostname translation for piggybacked hosts" to add a ' + 'organisation prefix to the hosts from the Cisco Meraki cloud to avoid conflicting ' + 'hostnames. You can also use this option along with the "Dynamic host management" to ' + 'sort the host in organisation specific folders.' + ) + )), + + "excluded_sections": DictElement( + parameter_form=MultipleChoice( + title=Title("Exclude Sections"), + elements=[ + MultipleChoiceElement(name=_SEC_NAME_ORG_API_REQUESTS, + title=Title(_SEC_TITLE_ORG_API_REQUESTS)), + MultipleChoiceElement(name=_SEC_NAME_APPLIANCE_UPLINKS, + title=Title(_SEC_TITLE_APPLIANCE_UPLINKS)), + MultipleChoiceElement(name=_SEC_NAME_APPLIANCE_UPLINKS_USAGE, + title=Title(_SEC_TITLE_APPLIANCE_UPLINKS_USAGE)), + MultipleChoiceElement(name=_SEC_NAME_APPLIANCE_VPNS, title=Title(_SEC_TITLE_APPLIANCE_VPNS)), + MultipleChoiceElement(name=_SEC_NAME_APPLIANCE_PERFORMANCE, + title=Title(_SEC_TITLE_APPLIANCE_PERFORMANCE)), + MultipleChoiceElement(name=_SEC_NAME_CELLULAR_UPLINKS, + title=Title(_SEC_TITLE_CELLULAR_UPLINKS)), + MultipleChoiceElement(name=_SEC_NAME_DEVICE_STATUSES, title=Title(_SEC_TITLE_DEVICE_STATUSES)), + MultipleChoiceElement(name=_SEC_NAME_DEVICE_UPLINKS_INFO, + title=Title(_SEC_TITLE_DEVICE_UPLINKS_INFO)), + MultipleChoiceElement(name=_SEC_NAME_LICENSES_OVERVIEW, + title=Title(_SEC_TITLE_LICENSES_OVERVIEW)), + MultipleChoiceElement(name=_SEC_NAME_SENSOR_READINGS, title=Title(_SEC_TITLE_SENSOR_READINGS)), + MultipleChoiceElement(name=_SEC_NAME_SWITCH_PORTS_STATUSES, + title=Title(_SEC_TITLE_SWITCH_PORTS_STATUSES)), + MultipleChoiceElement(name=_SEC_NAME_WIRELESS_ETHERNET_STATUSES, + title=Title(_SEC_TITLE_WIRELESS_ETHERNET_STATUSES)), + MultipleChoiceElement(name=_SEC_NAME_WIRELESS_DEVICE_STATUS, + title=Title(_SEC_TITLE_WIRELESS_DEVICE_STATUS)), + MultipleChoiceElement(name=_SEC_NAME_ORG_SWITCH_PORTS_STATUSES, + title=Title(_SEC_TITLE_ORG_SWITCH_PORTS_STATUSES)), + ], + prefill=DefaultValue([ + _SEC_NAME_APPLIANCE_PERFORMANCE, + _SEC_NAME_SWITCH_PORTS_STATUSES, + _SEC_NAME_WIRELESS_DEVICE_STATUS, + _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, + ]), + # migrate=_migrate_to_valid_ident, + ), + required=True, + ), + "orgs": DictElement( + parameter_form=List( + element_template=String(macro_support=True), title=Title("Organizations"), + custom_validate=(DuplicateInList(),), + ), + ), + "cache_per_section": DictElement( + parameter_form=Dictionary( + title=Title("Set Cache time per section"), + elements={ + sec_name: DictElement( + parameter_form=Integer( + title=Title(sec_title), + prefill=DefaultValue(sec_cache), + unit_symbol="minutes", + custom_validate=(NumberInRange(min_value=0),) + ) + ) for sec_name, sec_title, sec_cache in [ + (_SEC_NAME_APPLIANCE_PERFORMANCE, _SEC_TITLE_APPLIANCE_PERFORMANCE, _SEC_CACHE_APPLIANCE_PERFORMANCE), + (_SEC_NAME_APPLIANCE_UPLINKS_USAGE, _SEC_TITLE_APPLIANCE_UPLINKS_USAGE, _SEC_CACHE_APPLIANCE_UPLINKS_USAGE), + (_SEC_NAME_APPLIANCE_UPLINKS, _SEC_TITLE_APPLIANCE_UPLINKS, _SEC_CACHE_APPLIANCE_UPLINKS), + (_SEC_NAME_APPLIANCE_VPNS, _SEC_TITLE_APPLIANCE_VPNS, _SEC_CACHE_APPLIANCE_VPNS), + (_SEC_NAME_CELLULAR_UPLINKS, _SEC_TITLE_CELLULAR_UPLINKS, _SEC_CACHE_CELLULAR_UPLINKS), + (_SEC_NAME_DEVICE_INFO, _SEC_TITLE_DEVICE_INFO, _SEC_CACHE_DEVICE_INFO), + (_SEC_NAME_DEVICE_STATUSES, _SEC_TITLE_DEVICE_STATUSES, _SEC_CACHE_DEVICE_STATUSES), + (_SEC_NAME_DEVICE_UPLINKS_INFO, _SEC_TITLE_DEVICE_UPLINKS_INFO, _SEC_CACHE_DEVICE_UPLINKS_INFO), + (_SEC_NAME_LICENSES_OVERVIEW, _SEC_TITLE_LICENSES_OVERVIEW, _SEC_CACHE_LICENSES_OVERVIEW), + (_SEC_NAME_NETWORKS, _SEC_TITLE_NETWORKS, _SEC_CACHE_NETWORKS), + (_SEC_NAME_ORG_API_REQUESTS, _SEC_TITLE_ORG_API_REQUESTS, _SEC_CACHE_ORG_API_REQUESTS), + (_SEC_NAME_ORG_SWITCH_PORTS_STATUSES, _SEC_TITLE_ORG_SWITCH_PORTS_STATUSES, _SEC_CACHE_ORG_SWITCH_PORTS_STATUSES), + (_SEC_NAME_ORGANISATIONS, _SEC_TITLE_ORGANISATIONS, _SEC_CACHE_ORGANISATIONS), + (_SEC_NAME_SENSOR_READINGS, _SEC_TITLE_SENSOR_READINGS, _SEC_CACHE_SENSOR_READINGS), + (_SEC_NAME_SWITCH_PORTS_STATUSES, _SEC_TITLE_SWITCH_PORTS_STATUSES, _SEC_CACHE_SWITCH_PORTS_STATUSES), + (_SEC_NAME_WIRELESS_DEVICE_STATUS, _SEC_TITLE_WIRELESS_DEVICE_STATUS, _SEC_CACHE_WIRELESS_DEVICE_STATUS), + (_SEC_NAME_WIRELESS_ETHERNET_STATUSES, _SEC_TITLE_WIRELESS_ETHERNET_STATUSES, _SEC_CACHE_WIRELESS_ETHERNET_STATUSES), + ] + } + ) + ), + "sections": DictElement( + parameter_form=MultipleChoice( + title=Title("Sections"), + elements=[ + MultipleChoiceElement( + name="licenses_overview", title=Title("Organization licenses overview") + ), + MultipleChoiceElement( + name="device_statuses", title=Title("Organization device statuses") + ), + MultipleChoiceElement( + name="sensor_readings", title=Title("Organization sensor readings") + ), + ], + # migrate=_migrate_to_valid_ident, + ), + render_only=True, + ), + }, + ) + + +rule_spec_cisco_meraki = SpecialAgent( + name="cisco_meraki", + title=Title("Cisco Meraki"), + topic=Topic.APPLICATIONS, + parameter_form=_form_special_agent_cisco_meraki, +) diff --git a/source/agents/special/agent_cisco_meraki b/source/cmk_plugins/collection/libexec/agent_cisco_meraki similarity index 80% rename from source/agents/special/agent_cisco_meraki rename to source/cmk_plugins/collection/libexec/agent_cisco_meraki index d4ddb888718631c6ff30bfe5d5a93feb362eef69..4f569d1b0075b74bfe5c38fec74c14b0d76034a1 100755 --- a/source/agents/special/agent_cisco_meraki +++ b/source/cmk_plugins/collection/libexec/agent_cisco_meraki @@ -5,7 +5,7 @@ import sys -from cmk.special_agents.agent_cisco_meraki import main +from cmk_addons.plugins.meraki.lib import agent if __name__ == "__main__": - sys.exit(main()) + sys.exit(agent.main()) diff --git a/source/cmk_plugins/collection/server_side_calls/cisco_meraki.py b/source/cmk_plugins/collection/server_side_calls/cisco_meraki.py new file mode 100644 index 0000000000000000000000000000000000000000..4f772f38eb0cbe25f75362eb1e7ab1e80654766c --- /dev/null +++ b/source/cmk_plugins/collection/server_side_calls/cisco_meraki.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# Copyright (C) 2022 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. + + +from collections.abc import Iterator, Sequence, Mapping + +from pydantic import BaseModel + +from cmk.server_side_calls.v1 import ( + EnvProxy, + HostConfig, + NoProxy, + replace_macros, + Secret, + SpecialAgentCommand, + SpecialAgentConfig, + URLProxy, +) + +__param = { + 'api_key': Secret( + id=139915660185968, + format='%s', + pass_safely=True + ), + 'no_cache': True, + 'org_id_as_prefix': True, + 'excluded_sections': [ + 'appliance_performance', + 'switch_ports_statuses', + 'wireless_device_status', + 'org_switch_ports_statuses' + ], + 'orgs': ['1234', '670771'], + 'cache_per_section': { + 'appliance_performance': 0, + 'appliance_uplinks_usage': 0, + 'appliance_uplinks': 60, + 'appliance_vpns': 60, + 'cellular_uplinks': 60, + 'device_status': 60, + 'device_uplinks_info': 60, + 'licenses_overview': 600, + 'networks': 600, + 'api_requests_by_organization': 0, + 'org_switch_ports_statuses': 0, + 'organisations': 600, + 'sensor_readings': 0, + 'switch_ports_statuses': 0, + 'wireless_device_status': 30, + 'wireless_ethernet_statuses': 30 + } +} + + +class CachePerSection(BaseModel): + appliance_performance: int | None = None + appliance_uplinks_usage: int | None = None + appliance_uplinks: int | None = None + appliance_vpns: int | None = None + cellular_uplinks: int | None = None + device_info: int | None = None + device_status: int | None = None + device_uplinks_info: int | None = None + licenses_overview: int | None = None + networks: int | None = None + api_requests_by_organization: int | None = None + org_switch_ports_statuses: int | None = None + organisations: int | None = None + sensor_readings: int | None = None + switch_ports_statuses: int | None = None + wireless_device_status: int | None = None + wireless_ethernet_statuses: int | None = None + + +class Params(BaseModel): + api_key: Secret + proxy: URLProxy | NoProxy | EnvProxy | None = None + sections: Sequence[str] | None = None + orgs: Sequence[str] | None = None + excluded_sections: Sequence[str] | None = None + org_id_as_prefix: bool | None = None + no_cache: bool | None = None + cache_per_section: CachePerSection | None = None + + +def _agent_cisco_meraki_parser(params: Mapping[str, object]) -> Params: + # if 'api-requests-by-organization' in params.get('excluded_sections', []): + # print(params) + return Params.model_validate(params) + + +def agent_cisco_meraki_arguments( + params: Params, + host_config: HostConfig, +) -> Iterator[SpecialAgentCommand]: + # print(params) + + args: list[str | Secret] = [ + host_config.name, + params.api_key.unsafe(), + ] + + match params.proxy: + case URLProxy(url=url): + args += ["--proxy", url] + case EnvProxy(): + args += ["--proxy", "FROM_ENVIRONMENT"] + case NoProxy(): + args += ["--proxy", "NO_PROXY"] + + if params.sections is not None: + args.append("--sections") + args += [s.replace("_", "-") for s in params.sections] + + if params.excluded_sections is not None: + args.append("--excluded-sections") + args += [s.replace("_", "-") for s in params.excluded_sections] + + if params.orgs is not None: + args.append("--orgs") + args += [replace_macros(org, host_config.macros) for org in params.orgs] + + if params.cache_per_section is not None: + args.append("--cache-per-section") + args += [ + str(cache_value) if cache_value is not None else str(default_cache) for + cache_value, default_cache in [ + (params.cache_per_section.appliance_performance, 0), + (params.cache_per_section.appliance_uplinks_usage, 0), + (params.cache_per_section.appliance_uplinks, 60), + (params.cache_per_section.appliance_vpns, 60), + (params.cache_per_section.cellular_uplinks, 60), + (params.cache_per_section.device_info, 60), + (params.cache_per_section.device_status, 60), + (params.cache_per_section.device_uplinks_info, 60), + (params.cache_per_section.licenses_overview, 600), + (params.cache_per_section.networks, 600), + (params.cache_per_section.api_requests_by_organization, 0), + (params.cache_per_section.org_switch_ports_statuses, 0), + (params.cache_per_section.organisations, 600), + (params.cache_per_section.sensor_readings, 0), + (params.cache_per_section.switch_ports_statuses, 0), + (params.cache_per_section.wireless_device_status, 30), + (params.cache_per_section.wireless_ethernet_statuses, 30), + ] + ] + # default=[0, 0, 60, 60, 60, 60, 60, 60, 600, 600, 0, 0, 600, 0, 0, 30, 30] + # print(args) + + if params.org_id_as_prefix is True: + args.append("--org-id-as-prefix") + + if params.no_cache is True: + args.append("--no-cache") + + yield SpecialAgentCommand(command_arguments=args) + + +special_agent_cisco_meraki = SpecialAgentConfig( + name="cisco_meraki", + # parameter_parser=Params.model_validate, + parameter_parser=_agent_cisco_meraki_parser, + commands_function=agent_cisco_meraki_arguments, +) diff --git a/source/gui/metrics/cisco_meraki.py b/source/gui/metrics/cisco_meraki.py deleted file mode 100644 index 75e896cba96487053d43dad8125dc19085a9fb18..0000000000000000000000000000000000000000 --- a/source/gui/metrics/cisco_meraki.py +++ /dev/null @@ -1,332 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2023-11-04 -# File : cisco_meraki.py (metrics) -# -# 2023-11-12: added wireless device status (channel, channel width, signal power) -# 2024-05-12: added switch port statuses and API return Codes -# 2024-06-24: fixed, wrong total dor SSID perfometer signal_power -> 30 - -from cmk.gui.i18n import _ - -from cmk.gui.plugins.metrics.utils import ( - check_metrics, - graph_info, - metric_info, - perfometer_info -) - -# -# license overview -# -metric_info['sum_licensed_devices'] = { - 'title': _('Licensed devices'), - 'unit': 'count', - 'color': '31/a', -} - -metric_info['remaining_time'] = { - 'title': _('Remaining time'), - 'unit': 's', - 'color': '26/a', -} - -graph_info['cisco_meraki.remaining_time'] = { - 'title': _('Cisco Meraki Licenses remaining time'), - 'metrics': [ - ('remaining_time', 'area'), - ], - 'scalars': [ - ('remaining_time:crit', _('crit')), - ('remaining_time:warn', _('warn')), - ], - 'range': (0, 'remaining_time:max'), -} - -graph_info['cisco_meraki.licensed_devices'] = { - 'title': _('Cisco Meraki Licensed devices'), - 'metrics': [ - ('sum_licensed_devices', 'area'), - ], - 'range': (0, 'sum_licensed_devices:max'), -} - -perfometer_info.append(('stacked', [ - { - 'type': 'logarithmic', - 'metric': 'remaining_time', - # 'half_value': 2592000.0, # ome month - 'half_value': 31104000.0, # ome year - 'exponent': 2, - }, - { - 'type': 'logarithmic', - 'metric': 'sum_licensed_devices', - 'half_value': 500.0, - 'exponent': 2, - } -])) - -# -# device_status -# -metric_info['last_reported'] = { - 'title': _('Last reported'), - 'unit': 's', - 'color': '26/a', -} - -graph_info['cisco_meraki.device_status'] = { - 'title': _('Cisco Meraki device status'), - 'metrics': [ - ('last_reported', 'area'), - ], - 'range': (0, 'last_reported:max'), - 'scalars': [ - ('last_reported:crit', _('crit')), - ('last_reported:warn', _('warn')), - ], -} - -perfometer_info.append({ - 'type': 'logarithmic', - 'metric': 'last_reported', - 'half_value': 7200, # 2 hours - 'exponent': 2, -}) - -# -# wireless devices status -# -metric_info["signal_power"] = { - "title": _("Power"), - "unit": "dbm", - "color": "#20c080", -} - -metric_info["channel_width"] = { - "title": _("Channel Width"), - "unit": "hz", - "color": "11/a", -} - -metric_info["channel"] = { - "title": _("Channel"), - "unit": "count", - "color": "21/a", -} - -graph_info['cisco_meraki.wireless_device_status.signal_power'] = { - 'title': _('Signal power'), - 'metrics': [ - ('signal_power', 'area'), - ], - 'range': (0, 'signal_power:max'), -} - -graph_info['cisco_meraki.wireless_device_status.channel_width'] = { - 'title': _('Channel Width'), - 'metrics': [ - ('channel_width', 'area'), - ], - 'range': (0, 'channel_width:max'), -} - -graph_info['cisco_meraki.wireless_device_status.channel'] = { - 'title': _('Channel'), - 'metrics': [ - ('channel', 'area'), - ], - 'range': (0, 'channel:max'), -} - -perfometer_info.append({ - 'type': 'linear', - 'segments': ['signal_power'], - 'total': 30, -}) - -# -# switch port statuses -# -# check_metrics['check_mk-cisco_meraki_organisations_api'] = { -# 'traffic_total': {'auto_graph': False}, -# 'traffic_sent': {'auto_graph': False}, -# 'traffic_received': {'auto_graph': False}, -# } -# -# metric_info["traffic_total"] = { -# "title": _("Total bandwidth"), -# "unit": "bits/s", -# "color": "11/a", -# } -# -# metric_info["traffic_sent"] = { -# "title": _("Output bandwidth"), -# "unit": "bits/s", -# "color": "#0080e0", -# } -# -# metric_info["traffic_received"] = { -# "title": _("Input bandwidth"), -# "unit": "bits/s", -# "color": "#00e060", -# } -# -# graph_info['cisco_meraki.switch_port_status.traffic'] = { -# 'title': _('Traffic'), -# 'metrics': [ -# ('traffic_received', 'area'), -# ('traffic_sent', '-area'), -# ], -# } -# perfometer_info.append( -# { -# "type": "dual", -# "perfometers": [ -# { -# "type": "logarithmic", -# "metric": "traffic_received", -# "half_value": 500000, -# "exponent": 5, -# }, -# { -# "type": "logarithmic", -# "metric": "traffic_sent", -# "half_value": 500000, -# "exponent": 5, -# }, -# ], -# } -# ) - -# -# API return Codes -# -check_metrics['check_mk-cisco_meraki_organisations_api'] = { - 'api_code_2xx': {'auto_graph': False}, - 'api_code_3xx': {'auto_graph': False}, - 'api_code_4xx': {'auto_graph': False}, - 'api_code_5xx': {'auto_graph': False}, -} - -metric_info["api_code_2xx"] = { - "title": _("Code 2xx"), - "unit": "count", - "color": "#00e060", -} -metric_info["api_code_3xx"] = { - "title": _("Code 3xx"), - "unit": "count", - "color": "#20e060", -} -metric_info["api_code_4xx"] = { - "title": _("Code 4xx"), - "unit": "count", - "color": "#0080e0", -} -metric_info["api_code_5xx"] = { - "title": _("Code 5xx"), - "unit": "count", - "color": "#2080e0", -} - - -graph_info['cisco_meraki.cisco_meraki_organisations_api.code'] = { - 'title': _('Cisco Meraki API response codes'), - 'metrics': [ - ('api_code_2xx', 'line'), - ('api_code_3xx', 'line'), - ('api_code_4xx', '-line'), - ('api_code_5xx', '-line'), - ], - 'optional_metrics': [ - 'api_code_2xx', - 'api_code_3xx', - 'api_code_4xx', - 'api_code_5xx', - ] -} -perfometer_info.append( - { - "type": "stacked", - "perfometers": [ - { - "type": "logarithmic", - "metric": "api_code_2xx", - "half_value": 100, - "exponent": 5, - }, - { - "type": "logarithmic", - "metric": "api_code_4xx", - "half_value": 100, - "exponent": 5, - }, - ], - } -) -perfometer_info.append({ - "type": "logarithmic", - "metric": "api_code_2xx", - "half_value": 100, - "exponent": 5, -}) -perfometer_info.append({ - "type": "logarithmic", - "metric": "api_code_4xx", - "half_value": 100, - "exponent": 5, -}) - -# appliance performance/utitlization -metric_info["utilization"] = { - "title": _("Utilization"), - "unit": "%", - "color": "16/a", -} -graph_info['cisco_meraki.cisco_meraki_appliance.utilization'] = { - 'title': _('Cisco Meraki Appliance Utilization'), - 'metrics': [ - ('utilization', 'area'), - ], - 'scalars': [ - ('utilization:crit', _('crit')), - ('utilization:warn', _('warn')), - ], - 'range': (0, 100), -} -perfometer_info.append({ - 'type': 'linear', - 'segments': ['utilization'], - 'total': 100, -}) - -# testing only -# metric_info["usage_out"] = { -# "title": _("Usage Out"), -# "unit": "count", -# "color": "#0080e0", -# } -# -# metric_info["usage_in"] = { -# "title": _("Usage In"), -# "unit": "count", -# "color": "#00e060", -# } -# -# graph_info['cisco_meraki.switch_port_status.usage'] = { -# 'title': _('Usage'), -# 'metrics': [ -# ('usage_in', 'area'), -# ('usage_out', '-area'), -# ], -# 'optional_metrics': [ -# 'usage_in', -# 'usage_out', -# ] -# } diff --git a/source/gui/wato/check_parameters/cisco_meraki_org_appliance_performance.py b/source/gui/wato/check_parameters/cisco_meraki_org_appliance_performance.py deleted file mode 100644 index 715f7f29ce31753af67576ae337e6585920ce183..0000000000000000000000000000000000000000 --- a/source/gui/wato/check_parameters/cisco_meraki_org_appliance_performance.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2024-06-23 -# File : cisco_meraki_org_appliance_performance.py (WATO) - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithoutItem, - rulespec_registry, - RulespecGroupCheckParametersNetworking, -) -from cmk.gui.valuespec import ( - Dictionary, - Integer, - Tuple, -) - - -def _parameter_valuespec_cisco_meraki_org_appliance_performance(): - return Dictionary( - title=_('Cisco Meraki Appliance Utilization'), - optional_keys=True, - elements=[ - ('levels_upper', - Tuple( - title=_('Upper Levels'), - elements=[ - Integer( - title=_("Warning at"), - unit='%', - default_value=60, - minvalue=0, - maxvalue=101, - ), - Integer( - title=_("Critical at"), - unit='%', - default_value=80, - minvalue=0, - maxvalue=101, - ), - ], - help=_( - 'The device utilization data reported to the Meraki' - ' dashboard is based on a load average measured over a' - ' period of one minute. The load value is returned in' - ' numeric values ranging from 1 through 100. A lower' - ' value indicates a lower load, and a higher value' - ' indicates a more intense workload. Currently, the' - ' device utilization value is calculated based upon the' - ' CPU utilization of the MX as well as its traffic load.' - ' If an MX device is consistently over 50% utilization' - ' during normal operation, upgrading to a higher' - ' throughput model or reducing the per-device load' - ' through horizontal scaling should be considered. For' - ' more information see:' - ' https://documentation.meraki.com-MX-Monitoring?and?' - 'Reporting-Device?Utiliyation'), - )), - ], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithoutItem( - title=lambda: _('Cisco Meraki Appliance Utilization'), - check_group_name='cisco_meraki_org_appliance_performance', - group=RulespecGroupCheckParametersNetworking, - parameter_valuespec=_parameter_valuespec_cisco_meraki_org_appliance_performance, - match_type='dict', - ) -) diff --git a/source/gui/wato/check_parameters/cisco_meraki_org_appliance_uplinks.py b/source/gui/wato/check_parameters/cisco_meraki_org_appliance_uplinks.py deleted file mode 100644 index f0ef96ca17ec47137dcd3aa225532e45e94f2941..0000000000000000000000000000000000000000 --- a/source/gui/wato/check_parameters/cisco_meraki_org_appliance_uplinks.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2023-11-05 -# File : cisco_meraki_org_appliance_uplinks.py (WATO) - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithItem, - RulespecGroupCheckParametersNetworking, - rulespec_registry, -) -from cmk.gui.valuespec import ( - Dictionary, - FixedValue, - MonitoringState, - TextInput, -) - - -def _parameter_valuespec_cisco_meraki_org_appliance_uplinks(): - return Dictionary( - title=_('Cisco Meraki Appliance Uplinks'), - optional_keys=True, - elements=[ - ('status_map', - Dictionary( - title=_('Map uplink status to monitoring state'), - elements=[ - ("active", - MonitoringState( - title=_('Monitoring state for uplink state "active"'), - default_value=0, - )), - ("ready", - MonitoringState( - title=_('Monitoring state for uplink state "ready"'), - default_value=1, - )), - ("not connected", - MonitoringState( - title=_('Monitoring state for uplink state "not connected"'), - default_value=2, - )), - ("failed", - MonitoringState( - title=_('Monitoring state for uplink state "failed"'), - default_value=2, - )), - ] - )), - # not needed, if we don't want usage -> disable in agent - # ('show_traffic', - # FixedValue( - # True, - # title=_('Show bandwidth (use only with cache disabled)'), - # totext='Bandwidth monitoring enabled', - # help=_( - # 'Use only with cache disabled in the Meraki special agent settings. ' - # 'The throughput be based on the usage for the last 60 seconds.' - # ) - # )) - ], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - title=lambda: _('Cisco Meraki Appliance uplinks'), - check_group_name='cisco_meraki_org_appliance_uplinks', - group=RulespecGroupCheckParametersNetworking, - parameter_valuespec=_parameter_valuespec_cisco_meraki_org_appliance_uplinks, - match_type='dict', - item_spec=lambda: TextInput( - title=_('The Uplink name'), - ), - ) -) diff --git a/source/gui/wato/check_parameters/cisco_meraki_org_appliance_vpns.py b/source/gui/wato/check_parameters/cisco_meraki_org_appliance_vpns.py deleted file mode 100644 index f3759c889ccc5ad8ddaf09e85c6bd6382b053189..0000000000000000000000000000000000000000 --- a/source/gui/wato/check_parameters/cisco_meraki_org_appliance_vpns.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2023-11-05 -# File : cisco_meraki_org_appliance_vpns.py (WATO) - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithItem, - rulespec_registry, - RulespecGroupCheckParametersNetworking, -) -from cmk.gui.valuespec import ( - Dictionary, - TextInput, - MonitoringState, -) - - -def _parameter_valuespec_cisco_meraki_org_appliance_vpns(): - return Dictionary( - title=_('Cisco Meraki Appliance VPNs'), - optional_keys=True, - elements=[ - ('status_not_reachable', - MonitoringState( - title=_('Monitoring state if the VPN peer is not "reachable"'), - default_value=1, - )), - ], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - title=lambda: _('Cisco Meraki Appliance VPNs'), - check_group_name='cisco_meraki_org_appliance_vpns', - group=RulespecGroupCheckParametersNetworking, - parameter_valuespec=_parameter_valuespec_cisco_meraki_org_appliance_vpns, - match_type='dict', - item_spec=lambda: TextInput( - title=_('The peer name'), - ), - ) -) diff --git a/source/gui/wato/check_parameters/cisco_meraki_org_device_status.py b/source/gui/wato/check_parameters/cisco_meraki_org_device_status.py index b307596c5715ee14306475d289b00be68571b832..f24626578914de05fef76a0102ff1cb11d0a2653 100644 --- a/source/gui/wato/check_parameters/cisco_meraki_org_device_status.py +++ b/source/gui/wato/check_parameters/cisco_meraki_org_device_status.py @@ -8,9 +8,10 @@ # Date : 2023-11-04 # File : cisco_meraki_org_device_status.py (WATO) +# 2024-06-30: moved power supply part to cisco_meraki_org_device_status_ps.py (shadow built-in file) + from cmk.gui.i18n import _ from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithItem, CheckParameterRulespecWithoutItem, rulespec_registry, RulespecGroupCheckParametersHardware, @@ -18,7 +19,6 @@ from cmk.gui.plugins.wato.utils import ( from cmk.gui.valuespec import ( Integer, Dictionary, - TextInput, Tuple, MonitoringState, ) @@ -79,34 +79,3 @@ rulespec_registry.register( match_type="dict", ) ) - - -# -# Cisco Meraki Power Supply -# -def _parameter_valuespec_cisco_meraki_device_status_ps(): - return Dictionary( - title=_("Cisco Meraki Powersupply status"), - optional_keys=True, - elements=[ - ("state_not_powering", - MonitoringState( - title=_('Monitoring state if power supply is not "powering"'), - default_value=1, - )), - ], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - title=lambda: _("Cisco Meraki Power supply"), - check_group_name="cisco_meraki_device_status_ps", - group=RulespecGroupCheckParametersHardware, - parameter_valuespec=_parameter_valuespec_cisco_meraki_device_status_ps, - match_type="dict", - item_spec=lambda: TextInput( - title=_("The Slot number"), - ), - ) -) diff --git a/source/gui/wato/check_parameters/cisco_meraki_org_device_status_ps.py b/source/gui/wato/check_parameters/cisco_meraki_org_device_status_ps.py new file mode 100644 index 0000000000000000000000000000000000000000..335204a6c94d550cd8218e7e2807dcf73c3d09ea --- /dev/null +++ b/source/gui/wato/check_parameters/cisco_meraki_org_device_status_ps.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-06-30 +# File : cisco_meraki_org_device_status_ps.py (WATO) + +# 2024-06-30: created to shadow built-in file -> move rule from "Applications, Processes & Services" to "Hardware, BIOS" + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithItem, + CheckParameterRulespecWithoutItem, + rulespec_registry, + RulespecGroupCheckParametersHardware, +) +from cmk.gui.valuespec import ( + Dictionary, + TextInput, + MonitoringState, +) + +# +# Cisco Meraki Power Supply -> now built-in in cmk 2.3 +# +def _parameter_valuespec_cisco_meraki_device_status_ps(): + return Dictionary( + title=_("Cisco Meraki Powersupply status"), + optional_keys=True, + elements=[ + ("state_not_powering", + MonitoringState( + title=_('Monitoring state if power supply is not "powering"'), + default_value=1, + )), + ], + ) + + +rulespec_registry.register( + CheckParameterRulespecWithItem( + title=lambda: _("Cisco Meraki Power supply"), + check_group_name="cisco_meraki_device_status_ps", + group=RulespecGroupCheckParametersHardware, + parameter_valuespec=_parameter_valuespec_cisco_meraki_device_status_ps, + match_type="dict", + item_spec=lambda: TextInput( + title=_("Slot number"), + ), + ) +) diff --git a/source/gui/wato/check_parameters/cisco_meraki_org_wireless_device_status.py b/source/gui/wato/check_parameters/cisco_meraki_org_wireless_device_status.py deleted file mode 100644 index 5a408e9f9900ca2608100f908c32d7bbdfd4ad33..0000000000000000000000000000000000000000 --- a/source/gui/wato/check_parameters/cisco_meraki_org_wireless_device_status.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2024-02-02 -# File : cisco_meraki_org_wireless_status.py (WATO) - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithItem, - rulespec_registry, - RulespecGroupCheckParametersNetworking, -) -from cmk.gui.valuespec import ( - Dictionary, - TextInput, - MonitoringState, -) - - -def _parameter_valuespec_cisco_meraki_wireless_status(): - return Dictionary( - title=_('Cisco Meraki Appliance Uplinks'), - optional_keys=True, - elements=[ - ('state_if_not_enabled', - MonitoringState( - title=_('Monitoring state if SSID is "not enabled"'), - default_value=1, - )), - ], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - title=lambda: _('Cisco Meraki Wireless device'), - check_group_name='cisco_meraki_wireless_device_status', - group=RulespecGroupCheckParametersNetworking, - parameter_valuespec=_parameter_valuespec_cisco_meraki_wireless_status, - match_type='dict', - item_spec=lambda: TextInput(title=_('The SSID'), ), - ) -) diff --git a/source/gui/wato/check_parameters/cisco_meraki_organisations.py b/source/gui/wato/check_parameters/cisco_meraki_organisations.py deleted file mode 100644 index dcbd1a06f1fef0d93f4ae57e62222dcb6d7adc19..0000000000000000000000000000000000000000 --- a/source/gui/wato/check_parameters/cisco_meraki_organisations.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2023-11-11 -# File : cisco_meraki_organisations.py (wato plugin) - -# 2023-11-18: split from cisco_meraki_org_licenses_overview.py - - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - DropdownChoice, - HostRulespec, - RulespecGroupCheckParametersDiscovery, - rulespec_registry, -) -from cmk.gui.valuespec import ( - Dictionary, -) - - -def _valuespec_discovery_meraki_organisations(): - return Dictionary( - title=_('Cisco Meraki Organisations (API/Licenses)'), - elements=[ - ('item_variant', - DropdownChoice( - title=_('Information to use as item'), - help=_( - 'You can select how to build the item for this service. By default the Organisation ID/name\n' - 'is used to stay compatible with the build in check. The information not used for the item\n' - 'will be added to the service output.' - ), - choices=[ - ('org_id', 'Organisation ID'), - ('org_name', 'Organisation name'), - ('org_id_name', 'Organisation ID/name'), - ], - default_value='org_id_name', - )), - ], - required_keys=['item_variant'], - ) - - -rulespec_registry.register( - HostRulespec( - group=RulespecGroupCheckParametersDiscovery, - match_type='dict', - name='discovery_meraki_organisations', - valuespec=_valuespec_discovery_meraki_organisations, - )) diff --git a/source/gui/wato/check_parameters/cisco_meraki_organisations_api.py b/source/gui/wato/check_parameters/cisco_meraki_organisations_api.py deleted file mode 100644 index bc47b9c0c9bf80a077853e406aec55b9e2e77193..0000000000000000000000000000000000000000 --- a/source/gui/wato/check_parameters/cisco_meraki_organisations_api.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2023-11-18 -# File : cisco_meraki_organisations_api.py (wato plugin) - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithItem, - rulespec_registry, - RulespecGroupCheckParametersApplications, -) -from cmk.gui.valuespec import ( - Dictionary, - TextInput, - MonitoringState, -) - - -def _parameter_valuespec_cisco_meraki_organisations_api(): - return Dictionary( - title=_("Cisco Meraki Organisation API"), - optional_keys=True, - elements=[ - ("state_api_not_enabled", - MonitoringState( - title=_('Monitoring state if API is not enabled'), - default_value=1, - )), - ], - ignored_keys=[ - 'internal_item_name', - 'old_item_name', - 'item_variant', - ], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - title=lambda: _("Cisco Meraki Organisation API"), - check_group_name="cisco_meraki_organisations_api", - group=RulespecGroupCheckParametersApplications, - parameter_valuespec=_parameter_valuespec_cisco_meraki_organisations_api, - item_spec=lambda: TextInput(title=_("The organisation"), ), - match_type="dict", - ) -) diff --git a/source/gui/wato/check_parameters/cisco_meraki_switch_ports_statuses.py b/source/gui/wato/check_parameters/cisco_meraki_switch_ports_statuses.py deleted file mode 100644 index b312f6998a49d2141c7ad4f4c93edb480a2c8a17..0000000000000000000000000000000000000000 --- a/source/gui/wato/check_parameters/cisco_meraki_switch_ports_statuses.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2024-02-02 -# File : cisco_meraki_switch_ports_statuses.py (WATO) - -# 2024-05-12: added support for MerakiGetOrganizationSwitchPortsStatusesBySwitch (Early Access) -# added traffic counters as perfdata -# 2024-05-19: reworked switch port traffic -# 2024-05-20: added discovery rule for port status - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.utils import ( - CheckParameterRulespecWithItem, - HostRulespec, - RulespecGroupCheckParametersDiscovery, - RulespecGroupCheckParametersNetworking, - rulespec_registry, -) -from cmk.gui.valuespec import ( - Dictionary, - ListChoice, - FixedValue, - MonitoringState, - TextInput, -) - -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - _SEC_NAME_SWITCH_PORTS_STATUSES, # type: ignore[import] - # Early Access - _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, # type: ignore[import] -) - - -def _parameter_valuespec_cisco_meraki_switch_ports_statuses(): - return Dictionary( - title=_('Cisco Meraki Appliance Uplinks'), - optional_keys=True, - elements=[ - ('state_disabled', - MonitoringState( - title=_('Monitoring state if port is "disabled"'), - default_value=0, - )), - ('state_not_connected', - MonitoringState( - title=_('Monitoring state if port is "not connected"'), - default_value=0, - )), - ('state_not_full_duplex', - MonitoringState( - title=_('Monitoring state if port is "not full duplex"'), - default_value=1, - )), - ('state_speed_change', - MonitoringState( - title=_('Monitoring state if speed is changed'), - default_value=1, - )), - ('state_admin_change', - MonitoringState( - title=_('Monitoring state if admin state is changed'), - default_value=1, - )), - ('state_op_change', - MonitoringState( - title=_('Monitoring state if operational state is changed'), - default_value=1, - )), - ('show_traffic', - FixedValue( - True, - title=_('Show bandwidth (use only with cache disabled)'), - totext='Bandwidth monitoring enabled', - help=_( - 'Use only with cache disabled in the Meraki special agent settings. ' - 'Depending on your Meraki organization size (in terms of number of switches) ' - 'this will exceeds the limits of the allowed API requests per second. You can try to ' - 'enable "Early Access" in the Meraki dashboard. In the Meraki special agent settings ' - f'switch from "{_SEC_NAME_SWITCH_PORTS_STATUSES}" to "{_SEC_NAME_ORG_SWITCH_PORTS_STATUSES}". ' - 'This will fetch all the switch data with one API request instead of one request for each switch.' - ), - )), - ], - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - title=lambda: _('Cisco Meraki Switch Ports'), - check_group_name='cisco_meraki_switch_ports_statuses', - group=RulespecGroupCheckParametersNetworking, - parameter_valuespec=_parameter_valuespec_cisco_meraki_switch_ports_statuses, - match_type='dict', - item_spec=lambda: TextInput(title=_('The Port ID'), ), - ) -) - - - -def _valuespec_discovery_cisco_meraki_switch_ports_statuses(): - return Dictionary( - title=_('Cisco Meraki Switch Ports'), - elements=[ - ('discovered_port_states', - ListChoice( - title=_('Select Ports to discover'), - choices=[ - (True, _('Admin enabled')), - (False, _('Admin disabled')), - ('Connected', _('Connected')), - ('Disconnected', _('Disconnected')), - ], - help=_('Select the port states for discovery'), - default_value=[True, False, 'Connected', 'Disconnected'], - )), - ], - required_keys=['item_variant'], - ) - - -rulespec_registry.register( - HostRulespec( - group=RulespecGroupCheckParametersDiscovery, - match_type='dict', - name='discovery_cisco_meraki_switch_ports_statuses', - valuespec=_valuespec_discovery_cisco_meraki_switch_ports_statuses, - )) \ No newline at end of file diff --git a/source/packages/cisco_meraki b/source/packages/cisco_meraki index 6f2a23d4f3b30e271bb4e1c6f6d2d6732baebe1b..901bfc25b689e41b93cb83f470c7d76b0df8f090 100644 --- a/source/packages/cisco_meraki +++ b/source/packages/cisco_meraki @@ -22,45 +22,48 @@ '\n' 'For the Appliance Uplinks Usage and Wireless Devices Ethernet ' 'Statuses \n' - 'checks you need to update the Merkai SDK to version 1.39.0 at ' + 'checks you need to update the Meraki SDK to version 1.39.0 at ' 'least.\n' 'https://thl-cmk.hopto.org/gitlab/checkmk/cisco/meraki/cisco_meraki/-/raw/master/mkp/MerkaiSDK-1.39.0-202311-10.mkp\n' '\n' 'The latest SDK can be found here: ' 'https://github.com/meraki/dashboard-api-python\n', 'download_url': 'https://thl-cmk.hopto.org', - 'files': {'agent_based': ['utils/cisco_meraki.py', - 'cisco_meraki_org_appliance_uplinks.py', - 'cisco_meraki_org_appliance_vpns.py', - 'cisco_meraki_org_device_info.py', + 'files': {'agent_based': ['cisco_meraki_org_device_info.py', 'cisco_meraki_org_device_status.py', - 'cisco_meraki_org_device_uplinks.py', 'cisco_meraki_org_licenses_overview.py', - 'cisco_meraki_switch_ports_statuses.py', - 'cisco_meraki_org_wireless_device_status.py', - 'cisco_meraki_org_wireless_ethernet_statuses.py', - 'cisco_meraki_org_cellular_uplinks.py', - 'cisco_meraki_organisations_api.py', - 'cisco_meraki_org_networks.py', - 'cisco_meraki_org_appliance_performance.py'], - 'agents': ['special/agent_cisco_meraki'], - 'checks': ['agent_cisco_meraki'], - 'gui': ['metrics/cisco_meraki.py', - 'wato/check_parameters/cisco_meraki_org_appliance_uplinks.py', - 'wato/check_parameters/cisco_meraki_org_appliance_vpns.py', - 'wato/check_parameters/cisco_meraki_org_device_status.py', - 'wato/check_parameters/cisco_meraki_org_licenses_overviewi.py', - 'wato/check_parameters/cisco_meraki_organisations.py', - 'wato/check_parameters/cisco_meraki_organisations_api.py', - 'wato/check_parameters/cisco_meraki_org_wireless_device_status.py', - 'wato/check_parameters/cisco_meraki_switch_ports_statuses.py', - 'wato/check_parameters/cisco_meraki_org_appliance_performance.py'], - 'lib': ['python3/cmk/special_agents/agent_cisco_meraki.py'], - 'web': ['plugins/views/cisco_meraki.py', - 'plugins/wato/agent_cisco_meraki.py']}, + 'cisco_meraki_org_sensor_readings.py_'], + 'cmk_addons_plugins': ['meraki/agent_based/appliance_performance.py', + 'meraki/agent_based/appliance_uplinks.py', + 'meraki/agent_based/appliance_vpns.py', + 'meraki/agent_based/cellular_uplinks.py', + 'meraki/agent_based/device_uplinks.py', + 'meraki/agent_based/networks.py', + 'meraki/agent_based/organisations_api.py', + 'meraki/agent_based/switch_ports_statuses.py', + 'meraki/agent_based/wireless_device_ssid_status.py', + 'meraki/agent_based/wireless_ethernet_statuses.py', + 'meraki/graphing/packages.py', + 'meraki/lib/agent.py', + 'meraki/lib/utils.py', + 'meraki/rulesets/appliance_performance.py', + 'meraki/rulesets/appliance_uplinks.py', + 'meraki/rulesets/appliance_vpns.py', + 'meraki/rulesets/licenses_overviewi.py', + 'meraki/rulesets/organisations.py', + 'meraki/rulesets/organisations_api.py', + 'meraki/rulesets/switch_ports_statuses.py', + 'meraki/rulesets/wireless_device_ssid_status.py'], + 'cmk_plugins': ['cisco/rulesets/meraki.py', + 'collection/libexec/agent_cisco_meraki', + 'collection/server_side_calls/cisco_meraki.py'], + 'gui': ['wato/check_parameters/cisco_meraki_org_device_status.py', + 'wato/check_parameters/cisco_meraki_org_device_status_ps.py', + 'wato/check_parameters/cisco_meraki_org_licenses_overviewi.py'], + 'web': ['plugins/views/cisco_meraki.py']}, 'name': 'cisco_meraki', 'title': 'Cisco Meraki special agent', - 'version': '1.3.2-20240626', - 'version.min_required': '2.2.0b1', - 'version.packaged': '2.2.0p27', - 'version.usable_until': '2.3.0b1'} + 'version': '1.3.2-20240660', + 'version.min_required': '2.3.0b1', + 'version.packaged': 'cmk-mkp-tool 0.2.0', + 'version.usable_until': '2.4.0b1'} diff --git a/source/web/plugins/views/cisco_meraki.py b/source/web/plugins/views/cisco_meraki.py index 89d1512e689249f5f1aac750bd88de6801f46ab4..9ec7646683492566bfc8c881df3e665e973d30cc 100644 --- a/source/web/plugins/views/cisco_meraki.py +++ b/source/web/plugins/views/cisco_meraki.py @@ -11,9 +11,8 @@ # 2023-11-17: moved file from local/lib/ structure to local/share/ structure to avoid errors in web.log # 2023-11-19: added MT device -from cmk.gui.views.inventory.registry import inventory_displayhints - from cmk.gui.i18n import _l +from cmk.gui.views.inventory.registry import inventory_displayhints inventory_displayhints.update({ '.networking.uplinks:': { @@ -118,9 +117,3 @@ inventory_displayhints.update({ '.software.applications.cisco_meraki.networks:*.tags': {'title': _l('Tags')}, '.software.applications.cisco_meraki.networks:*.is_bound_to_template': {'title': _l('Is bound to template')}, }) -# cleanup build in display hints -# inventory_displayhints.pop('.software.configuration.organisation') -# inventory_displayhints.pop('.software.configuration.organisation.organisation_id') -# inventory_displayhints.pop('.software.configuration.organisation.organisation_name') -# inventory_displayhints.pop('.software.configuration.organisation.network_id') -# inventory_displayhints.pop('.software.configuration.organisation.address') diff --git a/source/web/plugins/wato/agent_cisco_meraki.py b/source/web/plugins/wato/agent_cisco_meraki.py deleted file mode 100644 index 60192b5e871594ed67fe8e131dc12300146f1f41..0000000000000000000000000000000000000000 --- a/source/web/plugins/wato/agent_cisco_meraki.py +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2022 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. - -# -# NOTE: to use the updated cisco meraki special agent WATO via ~/local structure you need to remove/rename the -# original file (~/lib/check_mk/gui/plugins/wato/special_agents/cisco_meraki.py). At the moment you -# can not supersede a special agent wato file via ~/local structure :-( -# - -# enhancements by thl-cmk[at]outlook[dot]com, https://thl-cmk.hopto.org -# - added check for duplicate organisation IDs -# - added some (basic) online help -# - changed clarified title of "orgs" from "Organisations" to "Organisation IDs" -# - added section titles from utils/cisco_meraki.py (reuse) -# - added option for host prefix/suffix/case per organisation -> needed for dynamic host management -# - added section names from utils/cisco_meraki.py (reuse with special agent) -# 2023-11-18: moved from ~/local/lib/check_mk/gui/plugins/wato to ~/local/share/check_mk/web/plugins/wato -# changed sections to excluded_sections -# 2023-11-22: replaced host_suffix_prefix option by org_id_as_prefix -# changed excluded_sections option from DualListChoice to ListChoice to avoid the "Selected" header -# in conjunction with "excluded Sections" title -# 2024-05-15: added api_key to required_keys -# 2024-06-23: added cache time per section -> not nice but should work. - -from typing import List - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato.special_agents.common import ( - RulespecGroupDatasourceProgramsApps -) -from cmk.gui.plugins.wato.utils import ( - HostRulespec, - HTTPProxyReference, - IndividualOrStoredPassword, - MKUserError, - rulespec_registry, -) -from cmk.gui.valuespec import ( - Dictionary, - FixedValue, - Integer, - ListChoice, - ListOfStrings, - Tuple, - ValueSpec, -) -from cmk.base.plugins.agent_based.utils.cisco_meraki import ( - # _SEC_NAME_DEVICE_INFO, - # _SEC_NAME_NETWORKS, - # _SEC_NAME_ORGANISATIONS, - _SEC_NAME_ORG_API_REQUESTS, # type: ignore[import] - _SEC_NAME_LICENSES_OVERVIEW, # type: ignore[import] - _SEC_NAME_DEVICE_STATUSES, # type: ignore[import] - _SEC_NAME_SENSOR_READINGS, # type: ignore[import] - _SEC_NAME_DEVICE_UPLINKS_INFO, # type: ignore[import] - _SEC_NAME_APPLIANCE_UPLINKS, # type: ignore[import] - _SEC_NAME_APPLIANCE_UPLINKS_USAGE, # type: ignore[import] - _SEC_NAME_APPLIANCE_VPNS, # type: ignore[import] - _SEC_NAME_APPLIANCE_PERFORMANCE, - _SEC_NAME_SWITCH_PORTS_STATUSES, # type: ignore[import] - _SEC_NAME_WIRELESS_ETHERNET_STATUSES, # type: ignore[import] - _SEC_NAME_WIRELESS_DEVICE_STATUS, # type: ignore[import] - _SEC_NAME_CELLULAR_UPLINKS, # type: ignore[import] - # Early Access - _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, # type: ignore[import] -) - -_SEC_TITLE_DEVICE_INFO = _('Device info (Organization)') -_SEC_TITLE_NETWORKS = _('Network info (Organization)') -_SEC_TITLE_ORGANISATIONS = _('Organization (Agent)') -_SEC_TITLE_ORG_API_REQUESTS = _('API request (Organizaion)') -_SEC_TITLE_APPLIANCE_UPLINKS = _('Appliances uplinks (Organizaion)') -_SEC_TITLE_APPLIANCE_UPLINKS_USAGE = _( - 'Appliances uplinks usage (Organizaion)') -_SEC_TITLE_APPLIANCE_VPNS = _('Appliances VPNs (Organizaion)') -_SEC_TITLE_APPLIANCE_PERFORMANCE = _('Appliances Utilization (Device)') -_SEC_TITLE_CELLULAR_UPLINKS = _('Cellular devices uplinks (Organizaion)') -_SEC_TITLE_DEVICE_STATUSES = _('Devices status (Organizaion)') -_SEC_TITLE_DEVICE_UPLINKS_INFO = _('Devices uplink info (Organizaion)') -_SEC_TITLE_LICENSES_OVERVIEW = _('Licenses overview (Organizaion)') -_SEC_TITLE_SENSOR_READINGS = _('Sensors readings (Organizaion)') -_SEC_TITLE_SWITCH_PORTS_STATUSES = _('Switch ports status (Device)') -_SEC_TITLE_WIRELESS_ETHERNET_STATUSES = _( - 'Wireless devices ethernet status (Organizaion)') -_SEC_TITLE_WIRELESS_DEVICE_STATUS = _('Wireless devices SSIDs status (Device)') -_SEC_TITLE_ORG_SWITCH_PORTS_STATUSES = _( - 'Switch port status (Organizaion/Early Access)') - - -def _validate_orgs(value: List[str] | None, var_prefix: str): - # Check for duplicate Organisations - if value is None: - return - _p = list(set(value.copy())) - if len(_p) != len(value): - raise MKUserError(var_prefix, _('Duplicate Organisation found')) - - for org_id in value: - if not org_id.isdigit(): - raise MKUserError( - var_prefix, _( - f'Not a valid Organisation ID {org_id}. Organisation IDs' - ' are all digits' - ) - ) - - -def _valuespec_special_agent_cisco_meraki() -> ValueSpec: - return Dictionary( - title=_('Cisco Meraki'), - elements=[ - ('api_key', IndividualOrStoredPassword( - title=_('API Key'), - allow_empty=False, - help=_('The key to access the Cisco Meraki Cloud Rest API.') - )), - ('proxy', HTTPProxyReference(),), - ('no_cache', FixedValue( - value=True, - title=_('Disable Cache'), - totext=_(''), - help=_( - 'Never use cached information. By default the agent will cache received ' - 'data to avoid API limits and speed up the data retrievel.' - ) - )), - ('org_id_as_prefix', FixedValue( - value=True, - title=_('Uese organisation ID as host prefix'), - totext=_(''), - help=_( - 'The organisation ID will be used as prefix for the hostname (separated by a "\'"). Use ' - 'this option together with a "Hostname translation for piggybacked hosts" to add a organisation ' - 'prefix to the hosts from the Cisco Meraki cloud to avoid conflicting hostnames. You can also use ' - 'this option along with the "Dynamic host management" to sort the host in organisation specific ' - 'folders.' - ) - )), - ('excluded_sections', - ListChoice( - title=_('excluded Sections'), - choices=[ - (_SEC_NAME_ORG_API_REQUESTS, _SEC_TITLE_ORG_API_REQUESTS), - (_SEC_NAME_APPLIANCE_UPLINKS, _SEC_TITLE_APPLIANCE_UPLINKS), - (_SEC_NAME_APPLIANCE_UPLINKS_USAGE, - _SEC_TITLE_APPLIANCE_UPLINKS_USAGE), - (_SEC_NAME_APPLIANCE_VPNS, _SEC_TITLE_APPLIANCE_VPNS), - (_SEC_NAME_APPLIANCE_PERFORMANCE, - _SEC_TITLE_APPLIANCE_PERFORMANCE), - (_SEC_NAME_CELLULAR_UPLINKS, _SEC_TITLE_CELLULAR_UPLINKS), - (_SEC_NAME_DEVICE_STATUSES, _SEC_TITLE_DEVICE_STATUSES), - (_SEC_NAME_DEVICE_UPLINKS_INFO, - _SEC_TITLE_DEVICE_UPLINKS_INFO), - (_SEC_NAME_LICENSES_OVERVIEW, _SEC_TITLE_LICENSES_OVERVIEW), - (_SEC_NAME_SENSOR_READINGS, _SEC_TITLE_SENSOR_READINGS), - (_SEC_NAME_SWITCH_PORTS_STATUSES, - _SEC_TITLE_SWITCH_PORTS_STATUSES), - (_SEC_NAME_WIRELESS_ETHERNET_STATUSES, - _SEC_TITLE_WIRELESS_ETHERNET_STATUSES), - (_SEC_NAME_WIRELESS_DEVICE_STATUS, - _SEC_TITLE_WIRELESS_DEVICE_STATUS), - (_SEC_NAME_ORG_SWITCH_PORTS_STATUSES, - _SEC_TITLE_ORG_SWITCH_PORTS_STATUSES), - ], - help=_( - 'Query only the selected sections. Default is Query all sections.'), - default_value=[ - _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, - _SEC_NAME_APPLIANCE_PERFORMANCE, - _SEC_NAME_SWITCH_PORTS_STATUSES, - _SEC_NAME_WIRELESS_DEVICE_STATUS, - ], - )), - ('orgs', - ListOfStrings( - title=_('Organisation IDs'), - help=_( - 'List of Organisation IDs to query. Defaulr is all Organisation IDs'), - allow_empty=False, - validate=_validate_orgs, - )), - ('cache_per_section', - Tuple( - title='Set Cache time per section', - elements=[ - Integer(title=_SEC_TITLE_APPLIANCE_PERFORMANCE, minvalue=0, unit='minutes', default_value=0), - Integer(title=_SEC_TITLE_APPLIANCE_UPLINKS_USAGE, minvalue=0, unit='minutes', default_value=0), - Integer(title=_SEC_TITLE_APPLIANCE_UPLINKS, minvalue=0, unit='minutes', default_value=60), - Integer(title=_SEC_TITLE_APPLIANCE_VPNS, minvalue=0, unit='minutes', default_value=60), - Integer(title=_SEC_TITLE_CELLULAR_UPLINKS, minvalue=0, unit='minutes', default_value=60), - Integer(title=_SEC_TITLE_DEVICE_INFO, minvalue=0, unit='minutes', default_value=60), - Integer(title=_SEC_TITLE_DEVICE_STATUSES, minvalue=0, unit='minutes', default_value=60), - Integer(title=_SEC_TITLE_DEVICE_UPLINKS_INFO, minvalue=0, unit='minutes', default_value=60), - Integer(title=_SEC_TITLE_LICENSES_OVERVIEW, minvalue=0, unit='minutes', default_value=600), - Integer(title=_SEC_TITLE_NETWORKS, minvalue=0, unit='minutes', default_value=600), - Integer(title=_SEC_TITLE_ORG_API_REQUESTS, minvalue=0, unit='minutes', default_value=0), - Integer(title=_SEC_TITLE_ORG_SWITCH_PORTS_STATUSES, minvalue=0, unit='minutes', default_value=0), - Integer(title=_SEC_TITLE_ORGANISATIONS, minvalue=0, unit='minutes', default_value=600), - Integer(title=_SEC_TITLE_SENSOR_READINGS, minvalue=0, unit='minutes', default_value=0), - Integer(title=_SEC_TITLE_SWITCH_PORTS_STATUSES, minvalue=0, unit='minutes', default_value=0), - Integer(title=_SEC_TITLE_WIRELESS_DEVICE_STATUS, minvalue=0, unit='minutes', default_value=30), - Integer(title=_SEC_TITLE_WIRELESS_ETHERNET_STATUSES, minvalue=0, unit='minutes', default_value=30), - ], - )) - ], - optional_keys=True, - ignored_keys=['sections', 'host_suffix_prefix'], - required_keys=['excluded_sections', 'api_key'], - ) - - -rulespec_registry.register( - HostRulespec( - group=RulespecGroupDatasourceProgramsApps, - name='special_agents:cisco_meraki', - valuespec=_valuespec_special_agent_cisco_meraki, - ) -)