Skip to content

Commit b253efb

Browse files
treasures(cisco_meraki): dump mkp source code
As discussed, this is the “upload” of the extended Cisco Meraki special agent files. I have stored the definition for the inventory view under ./view/ and, as agreed on, removed all dummy files to shadow the built-in agent. The functionality in this directory will be incrementally integrated into the existing cisco meraki plugin family in the coming months. CMK-27058 Closes: #867 Co-authored-by: Logan Connolly <logan.connolly@checkmk.com> Change-Id: I988e4bee956585708875e79295a75c5dac2736ad
1 parent 2492bad commit b253efb

31 files changed

+5649
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python3
2+
# Copyright (C) 2025 Checkmk GmbH - License: GNU General Public License v2
3+
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
4+
# conditions defined in the file COPYING, which is part of this source code package.
5+
#
6+
# Original author: thl-cmk[at]outlook[dot]com
7+
8+
from collections.abc import Mapping
9+
10+
from cmk.agent_based.v2 import (
11+
AgentSection,
12+
CheckPlugin,
13+
CheckResult,
14+
DiscoveryResult,
15+
Service,
16+
StringTable,
17+
check_levels,
18+
render,
19+
)
20+
21+
from cmk_addons.plugins.meraki.lib.utils import load_json
22+
23+
24+
# sample agent output
25+
# {"perfScore": 1}
26+
# sample string_table
27+
# [['[{"perfScore": 0}]']]
28+
# [['[{"perfScore": 12.0}]']]
29+
# [['[{"perfScore": 0.00021434677238992957}]']]
30+
def parse_appliance_performance(string_table: StringTable) -> float | None:
31+
json_data = load_json(string_table)
32+
33+
try:
34+
return float(json_data[0]['perfScore'])
35+
except (ValueError, TypeError, KeyError):
36+
return None
37+
38+
39+
agent_section_meraki_org_appliance_performance = AgentSection(
40+
name="cisco_meraki_org_appliance_performance",
41+
parse_function=parse_appliance_performance,
42+
)
43+
44+
45+
def discover_appliance_performance(section: float) -> DiscoveryResult:
46+
yield Service()
47+
48+
49+
def check_appliance_performance(params: Mapping[str, any], section: float) -> CheckResult:
50+
yield from check_levels(
51+
value=section,
52+
label='Utilization',
53+
levels_upper=params['levels_upper'],
54+
render_func=render.percent,
55+
metric_name='utilization',
56+
boundaries=(0, 100),
57+
)
58+
59+
60+
check_plugin_meraki_org_appliance_performance = CheckPlugin(
61+
name="cisco_meraki_org_appliance_performance",
62+
service_name="Utilization",
63+
check_function=check_appliance_performance,
64+
discovery_function=discover_appliance_performance,
65+
check_ruleset_name="cisco_meraki_org_appliance_performance",
66+
check_default_parameters={
67+
'levels_upper': ('fixed', (60, 80)),
68+
},
69+
)
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#!/usr/bin/env python3
2+
# Copyright (C) 2025 Checkmk GmbH - License: GNU General Public License v2
3+
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
4+
# conditions defined in the file COPYING, which is part of this source code package.
5+
#
6+
# Original author: thl-cmk[at]outlook[dot]com
7+
8+
from collections.abc import Mapping
9+
from dataclasses import dataclass
10+
from datetime import datetime
11+
12+
from cmk.agent_based.v2 import (
13+
AgentSection,
14+
CheckPlugin,
15+
CheckResult,
16+
DiscoveryResult,
17+
Result,
18+
Service,
19+
State,
20+
StringTable,
21+
check_levels,
22+
render,
23+
)
24+
25+
from cmk_addons.plugins.meraki.lib.utils import get_int, load_json
26+
27+
# sample string_table
28+
__appliance_uplinks = [
29+
{
30+
'highAvailability': {
31+
'enabled': False,
32+
'role': 'primary'
33+
},
34+
'lastReportedAt': '2023-11-05T08:18:21Z',
35+
'model': 'MX68',
36+
'networkId': 'L_6209337986237261234',
37+
'networkName': 'NetworkName',
38+
'serial': 'Q2KY-DCBA-ABCD',
39+
'uplinks': [
40+
{
41+
'gateway': '192.168.10.1',
42+
'interface': 'wan1',
43+
'ip': '192.168.10.2',
44+
'ipAssignedBy': 'static',
45+
'primaryDns': '192.168.10.1',
46+
'publicIp': '20.197.135.251',
47+
'secondaryDns': '9.9.9.9',
48+
'status': 'active',
49+
'received': 52320006, # bytes
50+
'sent': 52928038, # bytes
51+
},
52+
{
53+
'gateway': '192.168.5.100',
54+
'interface': 'wan2',
55+
'ip': '192.168.5.236',
56+
'ipAssignedBy': 'dhcp',
57+
'primaryDns': '192.168.5.100',
58+
'publicIp': '20.151.100.109',
59+
'secondaryDns': '0.0.0.0',
60+
'status': 'active'
61+
}
62+
]
63+
}
64+
]
65+
66+
_LAST_REPORTED_AT = '%Y-%m-%dT%H:%M:%SZ'
67+
68+
69+
@dataclass(frozen=True)
70+
class ApplianceUplink:
71+
gateway: str | None
72+
interface: str | None
73+
ip: str | None
74+
ip_assigned_by: str | None
75+
primary_dns: str | None
76+
public_ip: str | None
77+
received: int | None
78+
secondary_dns: str | None
79+
sent: int | None
80+
status: str | None
81+
82+
@classmethod
83+
def parse(cls, uplink: Mapping[str: object]):
84+
return cls(
85+
gateway=str(uplink['gateway']) if uplink.get('gateway') is not None else None,
86+
interface=str(uplink['interface']) if uplink.get('interface') is not None else None,
87+
ip=str(uplink['ip']) if uplink.get('ip') is not None else None,
88+
ip_assigned_by=str(uplink['ipAssignedBy']) if uplink.get('ipAssignedBy') is not None else None,
89+
primary_dns=str(uplink['primaryDns']) if uplink.get('primaryDns') is not None else None,
90+
public_ip=str(uplink['publicIp']) if uplink.get('publicIp') is not None else None,
91+
received=get_int(uplink.get('received')),
92+
secondary_dns=str(uplink['secondaryDns']) if uplink.get('secondaryDns') is not None else None,
93+
sent=get_int(uplink.get('sent')),
94+
status=str(uplink['status']) if uplink.get('status') is not None else None,
95+
)
96+
97+
98+
@dataclass(frozen=True)
99+
class ApplianceUplinkHA:
100+
enabled: bool | None
101+
role: str | None
102+
103+
@classmethod
104+
def parse(cls, high_availability: Mapping[str: object]):
105+
return cls(
106+
enabled=bool(high_availability['enabled']) if high_availability.get('enabled') is not None else None,
107+
role=str(high_availability['role']) if high_availability.get('role') is not None else None,
108+
)
109+
110+
111+
@dataclass(frozen=True)
112+
class Appliance:
113+
high_availability: ApplianceUplinkHA | None
114+
last_reported_at: datetime | None
115+
model: str | None
116+
network_name: str | None
117+
serial: str | None
118+
uplinks: Mapping[str, ApplianceUplink] | None
119+
120+
@classmethod
121+
def parse(cls, appliance: Mapping[str: object]):
122+
return cls(
123+
high_availability=ApplianceUplinkHA.parse(appliance['highAvailability']) if appliance.get(
124+
'highAvailability') is not None else None,
125+
last_reported_at=datetime.strptime(appliance['lastReportedAt'], _LAST_REPORTED_AT) if appliance[
126+
'lastReportedAt'] else None,
127+
model=str(appliance['model']) if appliance.get('model') is not None else None,
128+
network_name=str(appliance['networkName']) if appliance.get('networkName') is not None else None,
129+
serial=str(appliance['serial']) if appliance.get('serial') is not None else None,
130+
uplinks={uplink['interface']: ApplianceUplink.parse(uplink) for uplink in appliance.get('uplinks', [])},
131+
)
132+
133+
134+
def parse_appliance_uplinks(string_table: StringTable) -> Appliance | None:
135+
json_data = load_json(string_table)
136+
return Appliance.parse(json_data[0])
137+
138+
139+
agent_section_cisco_meraki_org_appliance_uplinks = AgentSection(
140+
name='cisco_meraki_org_appliance_uplinks',
141+
parse_function=parse_appliance_uplinks,
142+
)
143+
144+
145+
def discover_appliance_uplinks(section: Appliance) -> DiscoveryResult:
146+
for uplink in section.uplinks.keys():
147+
yield Service(item=uplink)
148+
149+
150+
_STATUS_MAP = {
151+
'active': 0,
152+
'failed': 2,
153+
'not_connected': 1,
154+
'ready': 0,
155+
'connecting': 1,
156+
}
157+
_TIMESPAN = 60
158+
159+
160+
def render_network_bandwidth_bits(value: int) -> str:
161+
return render.networkbandwidth(value/8)
162+
163+
164+
def check_appliance_uplinks(item: str, params: Mapping[str, any], section: Appliance) -> CheckResult:
165+
if (uplink := section.uplinks.get(item)) is None:
166+
return None
167+
168+
if params.get('status_map'):
169+
_STATUS_MAP.update(params['status_map'])
170+
_STATUS_MAP['not connected'] = _STATUS_MAP['not_connected'] # can not use 'nor connected' in params anymore :-(
171+
172+
yield Result(state=State(_STATUS_MAP.get(uplink.status, 3)), summary=f'Status: {uplink.status}')
173+
if uplink.ip:
174+
yield Result(state=State.OK, summary=f'IP: {uplink.ip}')
175+
if uplink.public_ip:
176+
yield Result(state=State.OK, summary=f'Public IP: {uplink.public_ip}')
177+
yield Result(state=State.OK, notice=f'Network: {section.network_name}')
178+
179+
if params.get('show_traffic') and uplink.status in ['active']: # we can only have traffic, if uplink is connected
180+
if uplink.received: # and params.get('show_traffic'):
181+
value = uplink.received * 8 / _TIMESPAN # Bits / Timespan
182+
yield from check_levels(
183+
value=value, # Bits
184+
label='In',
185+
metric_name='if_in_bps',
186+
render_func=render_network_bandwidth_bits, # Bytes
187+
)
188+
189+
if uplink.sent: # and params.get('show_traffic'):
190+
value = uplink.sent * 8 / _TIMESPAN # Bits / Timespan
191+
yield from check_levels(
192+
value=value, # Bits
193+
label='Out',
194+
metric_name='if_out_bps',
195+
render_func=render_network_bandwidth_bits, # Bytes
196+
)
197+
198+
# not needed, will show in device status (?)
199+
# yield from check_last_reported_ts(last_reported_ts=section.last_reported_at.timestamp())
200+
201+
# not sure if this is usefully, need system with H/A enabled=True to check
202+
yield Result(state=State.OK, notice=f'H/A enabled: {section.high_availability.enabled}')
203+
yield Result(state=State.OK, notice=f'H/A role: {section.high_availability.role}')
204+
205+
if uplink.gateway:
206+
yield Result(state=State.OK, notice=f'Gateway: {uplink.gateway}')
207+
if uplink.ip_assigned_by:
208+
yield Result(state=State.OK, notice=f'IP assigned by: {uplink.ip_assigned_by}')
209+
if uplink.primary_dns:
210+
yield Result(state=State.OK, notice=f'Primary DNS: {uplink.primary_dns}')
211+
if uplink.secondary_dns:
212+
yield Result(state=State.OK, notice=f'Secondary DNS: {uplink.secondary_dns}')
213+
214+
215+
check_plugin_cisco_meraki_org_appliance_uplinks = CheckPlugin(
216+
name='cisco_meraki_org_appliance_uplinks',
217+
service_name='Uplink %s',
218+
discovery_function=discover_appliance_uplinks,
219+
check_function=check_appliance_uplinks,
220+
check_default_parameters={},
221+
check_ruleset_name='cisco_meraki_org_appliance_uplinks',
222+
)

0 commit comments

Comments
 (0)