mirror of
https://github.com/bashclub/check-unifi-controller.git
synced 2024-12-25 12:50:12 +01:00
Initial Version 0.75
This commit is contained in:
parent
123ea559e3
commit
aeb423bf0b
532
lib/check_mk/base/plugins/agent_based/unifi_controller.py
Normal file
532
lib/check_mk/base/plugins/agent_based/unifi_controller.py
Normal file
@ -0,0 +1,532 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||||
|
#
|
||||||
|
from cmk.gui.i18n import _
|
||||||
|
|
||||||
|
from .agent_based_api.v1 import (
|
||||||
|
Metric,
|
||||||
|
register,
|
||||||
|
render,
|
||||||
|
Result,
|
||||||
|
Service,
|
||||||
|
State,
|
||||||
|
)
|
||||||
|
from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult
|
||||||
|
from typing import Any, Dict, Mapping, Sequence, Optional
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from collections import defaultdict
|
||||||
|
from .utils import interfaces
|
||||||
|
|
||||||
|
SubSection = Dict[str,str]
|
||||||
|
Section = Dict[str, SubSection]
|
||||||
|
class dictobject(defaultdict):
|
||||||
|
def __getattr__(self,name):
|
||||||
|
return self[name] if name in self else ""
|
||||||
|
|
||||||
|
nested_dictobject = lambda: dictobject(nested_dictobject)
|
||||||
|
|
||||||
|
def _expect_bool(val,expected=True,failstate=State.WARN):
|
||||||
|
return State.OK if bool(int(val)) == expected else failstate
|
||||||
|
|
||||||
|
def _expect_number(val,expected=0,failstate=State.WARN):
|
||||||
|
return State.OK if int(val) == expected else failstate
|
||||||
|
|
||||||
|
def _safe_float(val):
|
||||||
|
try:
|
||||||
|
return float(val)
|
||||||
|
except (TypeError,ValueError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _unifi_status2state(status):
|
||||||
|
return {
|
||||||
|
"ok" : State.OK,
|
||||||
|
"warning" : State.WARN,
|
||||||
|
"error" : State.CRIT
|
||||||
|
}.get(status.lower(),State.UNKNOWN)
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
def parse_unifi_dict(string_table):
|
||||||
|
_ret = dictobject()
|
||||||
|
for _line in string_table:
|
||||||
|
_ret[_line[0]] = _line[1]
|
||||||
|
return _ret
|
||||||
|
|
||||||
|
def parse_unifi_nested_dict(string_table):
|
||||||
|
_ret = nested_dictobject()
|
||||||
|
for _line in string_table:
|
||||||
|
_ret[_line[0]][_line[1]] = _line[2]
|
||||||
|
return _ret
|
||||||
|
|
||||||
|
############ Controller ############
|
||||||
|
def discovery_unifi_controller(section):
|
||||||
|
yield Service(item="Unifi Controller")
|
||||||
|
if section.cloudkey_version:
|
||||||
|
yield Service(item="Cloudkey")
|
||||||
|
|
||||||
|
def check_unifi_controller(item,section):
|
||||||
|
if item == "Unifi Controller":
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Version: {section.controller_version}"
|
||||||
|
)
|
||||||
|
if int(section.update_available) > 0:
|
||||||
|
yield Result(
|
||||||
|
state=State.WARN,
|
||||||
|
notice=_("Update available")
|
||||||
|
)
|
||||||
|
yield Metric("uptime",int(section.uptime))
|
||||||
|
if item == "Cloudkey":
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Version: {section.cloudkey_version}"
|
||||||
|
)
|
||||||
|
if int(section.cloudkey_update_available) > 0:
|
||||||
|
yield Result(
|
||||||
|
state=State.WARN,
|
||||||
|
notice=_("Update available")
|
||||||
|
)
|
||||||
|
|
||||||
|
register.agent_section(
|
||||||
|
name = 'unifi_controller',
|
||||||
|
parse_function = parse_unifi_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
register.check_plugin(
|
||||||
|
name='unifi_controller',
|
||||||
|
service_name='%s',
|
||||||
|
discovery_function=discovery_unifi_controller,
|
||||||
|
check_function=check_unifi_controller,
|
||||||
|
)
|
||||||
|
############ SITES ###########
|
||||||
|
|
||||||
|
def discovery_unifi_sites(section):
|
||||||
|
for _item in section.values():
|
||||||
|
yield Service(item=f"{_item.desc}")
|
||||||
|
|
||||||
|
def check_unifi_sites(item,section):
|
||||||
|
site = next(filter(lambda x: x.desc == item,section.values()))
|
||||||
|
yield Metric("satisfaction",max(0,interfaces.saveint(site.satisfaction)))
|
||||||
|
|
||||||
|
if site.lan_status != "unknown":
|
||||||
|
yield Metric("lan_user_sta",interfaces.saveint(site.lan_num_user))
|
||||||
|
yield Metric("lan_guest_sta",interfaces.saveint(site.lan_num_guest))
|
||||||
|
yield Metric("if_in_octets",interfaces.saveint(site.lan_rx_bytes_r))
|
||||||
|
#yield Metric("if_in_bps",interfaces.saveint(site.lan_rx_bytes_r)*8)
|
||||||
|
yield Metric("if_out_octets",interfaces.saveint(site.lan_tx_bytes_r))
|
||||||
|
#yield Metric("if_out_bps",interfaces.saveint(site.lan_tx_bytes_r)*8)
|
||||||
|
|
||||||
|
yield Result(
|
||||||
|
state=_unifi_status2state(site.lan_status),
|
||||||
|
summary=f"LAN: {site.lan_num_sw}/{site.lan_num_adopted} Switch ({site.lan_status})"
|
||||||
|
)
|
||||||
|
#yield Result(
|
||||||
|
# state=_expect_number(site.lan_num_disconnected),
|
||||||
|
# notice=f"{site.lan_num_disconnected} Switch disconnected" ##disconnected kann
|
||||||
|
#)
|
||||||
|
|
||||||
|
if site.wlan_status != "unknown":
|
||||||
|
yield Metric("wlan_user_sta",interfaces.saveint(site.wlan_num_user))
|
||||||
|
yield Metric("wlan_guest_sta",interfaces.saveint(site.wlan_num_guest))
|
||||||
|
yield Metric("wlan_iot_sta",interfaces.saveint(site.wlan_num_iot))
|
||||||
|
yield Metric("wlan_if_in_octets",interfaces.saveint(site.wlan_rx_bytes_r))
|
||||||
|
yield Metric("wlan_if_out_octets",interfaces.saveint(site.wlan_tx_bytes_r))
|
||||||
|
yield Result(
|
||||||
|
state=_unifi_status2state(site.wlan_status),
|
||||||
|
summary=f"WLAN: {site.wlan_num_ap}/{site.wlan_num_adopted} AP ({site.wlan_status})"
|
||||||
|
)
|
||||||
|
#yield Result(
|
||||||
|
# state=_expect_number(site.wlan_num_disconnected),
|
||||||
|
# notice=f"{site.wlan_num_disconnected} AP disconnected"
|
||||||
|
#)
|
||||||
|
if site.wan_status != "unknown":
|
||||||
|
yield Result(
|
||||||
|
state=_unifi_status2state(site.wan_status),
|
||||||
|
summary=f"WAN Status: {site.wan_status}"
|
||||||
|
)
|
||||||
|
if site.www_status != "unknown":
|
||||||
|
yield Result(
|
||||||
|
state=_unifi_status2state(site.www_status),
|
||||||
|
summary=f"WWW Status: {site.www_status}"
|
||||||
|
)
|
||||||
|
if site.vpn_status != "unknown":
|
||||||
|
yield Result(
|
||||||
|
state=_unifi_status2state(site.vpn_status),
|
||||||
|
notice=f"WWW Status: {site.vpn_status}"
|
||||||
|
)
|
||||||
|
yield Result(
|
||||||
|
state=_expect_number(site.num_new_alarms),
|
||||||
|
notice=f"{site.num_new_alarms} new Alarm"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
register.agent_section(
|
||||||
|
name = 'unifi_sites',
|
||||||
|
parse_function = parse_unifi_nested_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
register.check_plugin(
|
||||||
|
name='unifi_sites',
|
||||||
|
service_name='Site %s',
|
||||||
|
discovery_function=discovery_unifi_sites,
|
||||||
|
check_function=check_unifi_sites,
|
||||||
|
)
|
||||||
|
|
||||||
|
############ DEVICE ###########
|
||||||
|
def discovery_unifi_device(section):
|
||||||
|
yield Service(item="Unifi Device")
|
||||||
|
yield Service(item="Uptime")
|
||||||
|
yield Service(item="Active-User")
|
||||||
|
if section.type != "uap": # kein satisfaction bei ap .. radio/ssid haben schon
|
||||||
|
yield Service(item="Satisfaction")
|
||||||
|
if section.general_temperature:
|
||||||
|
yield Service(item="Temperature")
|
||||||
|
if section.uplink_device:
|
||||||
|
yield Service(item="Uplink")
|
||||||
|
if section.speedtest_status:
|
||||||
|
yield Service(item="Speedtest")
|
||||||
|
|
||||||
|
def check_unifi_device(item,section):
|
||||||
|
if item == "Unifi Device":
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Version: {section.version}"
|
||||||
|
)
|
||||||
|
if interfaces.saveint(section.upgradable) > 0:
|
||||||
|
yield Result(
|
||||||
|
state=State.WARN,
|
||||||
|
notice=_("Update available")
|
||||||
|
)
|
||||||
|
if item == "Active-User":
|
||||||
|
_active_user = interfaces.saveint(section.user_num_sta)
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"{_active_user}"
|
||||||
|
)
|
||||||
|
if interfaces.saveint(section.guest_num_sta) > -1:
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Guest: {section.guest_num_sta}"
|
||||||
|
)
|
||||||
|
yield Metric("user_sta",_active_user)
|
||||||
|
yield Metric("guest_sta",interfaces.saveint(section.guest_num_sta))
|
||||||
|
if item == "Uptime":
|
||||||
|
_uptime = int(section.uptime) if section.uptime else -1
|
||||||
|
if _uptime > 0:
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=render.timespan(_uptime)
|
||||||
|
)
|
||||||
|
yield Metric("unifi_uptime",_uptime)
|
||||||
|
if item == "Satisfaction":
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"{section.satisfaction}%"
|
||||||
|
)
|
||||||
|
yield Metric("satisfaction",max(0,interfaces.saveint(section.satisfaction)))
|
||||||
|
if item == "Temperature":
|
||||||
|
yield Metric("temp",_safe_float(section.general_temperature))
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"{section.general_temperature} °C"
|
||||||
|
)
|
||||||
|
if section.fan_level:
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Fan: {section.fan_level}%"
|
||||||
|
)
|
||||||
|
if item == "Speedtest":
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Ping: {section.speedtest_ping} ms"
|
||||||
|
)
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Down: {section.speedtest_download} Mbit/s"
|
||||||
|
)
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Up: {section.speedtest_upload} Mbit/s"
|
||||||
|
)
|
||||||
|
_speedtest_time = render.datetime(_safe_float(section.speedtest_time))
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Last: {_speedtest_time}"
|
||||||
|
)
|
||||||
|
yield Metric("rtt",_safe_float(section.speedtest_ping))
|
||||||
|
yield Metric("if_in_bps",_safe_float(section.speedtest_download)*1024*1024) ## mbit to bit
|
||||||
|
yield Metric("if_out_bps",_safe_float(section.speedtest_upload)*1024*1024) ## mbit to bit
|
||||||
|
|
||||||
|
if item == "Uplink":
|
||||||
|
yield Result(
|
||||||
|
state=_expect_bool(section.uplink_up),
|
||||||
|
summary=f"Device {section.uplink_device} Port: {section.uplink_remote_port}"
|
||||||
|
)
|
||||||
|
|
||||||
|
register.agent_section(
|
||||||
|
name = 'unifi_device',
|
||||||
|
parse_function = parse_unifi_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
register.check_plugin(
|
||||||
|
name='unifi_device',
|
||||||
|
service_name='%s',
|
||||||
|
discovery_function=discovery_unifi_device,
|
||||||
|
check_function=check_unifi_device,
|
||||||
|
)
|
||||||
|
############ DEVICEPORT ###########
|
||||||
|
@dataclass
|
||||||
|
class unifi_interface(interfaces.Interface):
|
||||||
|
jumbo : bool = False
|
||||||
|
satisfaction : int = 0
|
||||||
|
poe_enable : bool = False
|
||||||
|
poe_mode : Optional[str] = None
|
||||||
|
poe_good : Optional[bool] = None
|
||||||
|
poe_current : Optional[float] = 0
|
||||||
|
poe_power : Optional[float] = 0
|
||||||
|
poe_voltage : Optional[float] = 0
|
||||||
|
poe_class : Optional[str] = None
|
||||||
|
dot1x_mode : Optional[str] = None
|
||||||
|
dot1x_status : Optional[str] = None
|
||||||
|
ip_address : Optional[str] = None
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
self.finalize()
|
||||||
|
|
||||||
|
def _convert_unifi_counters_if(section: Section) -> interfaces.Section:
|
||||||
|
return [
|
||||||
|
unifi_interface(
|
||||||
|
index=str(netif.port_idx),
|
||||||
|
descr=netif.name,
|
||||||
|
alias=netif.name,
|
||||||
|
type='6',
|
||||||
|
speed=interfaces.saveint(netif.speed)*1000000,
|
||||||
|
oper_status=netif.oper_status,
|
||||||
|
admin_status=netif.admin_status,
|
||||||
|
in_octets=interfaces.saveint(netif.rx_bytes),
|
||||||
|
in_ucast=interfaces.saveint(netif.rx_packets),
|
||||||
|
in_mcast=interfaces.saveint(netif.rx_multicast),
|
||||||
|
in_bcast=interfaces.saveint(netif.rx_broadcast),
|
||||||
|
in_discards=interfaces.saveint(netif.rx_dropped),
|
||||||
|
in_errors=interfaces.saveint(netif.rx_errors),
|
||||||
|
out_octets=interfaces.saveint(netif.tx_bytes),
|
||||||
|
out_ucast=interfaces.saveint(netif.tx_packets),
|
||||||
|
out_mcast=interfaces.saveint(netif.tx_multicast),
|
||||||
|
out_bcast=interfaces.saveint(netif.tx_broadcast),
|
||||||
|
out_discards=interfaces.saveint(netif.tx_dropped),
|
||||||
|
out_errors=interfaces.saveint(netif.tx_errors),
|
||||||
|
jumbo=True if netif.jumbo == "1" else False,
|
||||||
|
satisfaction=interfaces.saveint(netif.satisfaction) if netif.satisfaction and netif.oper_status == "1" else 0,
|
||||||
|
poe_enable=True if netif.poe_enable == "1" else False,
|
||||||
|
poe_mode=netif.poe_mode,
|
||||||
|
poe_current=float(netif.poe_current) if netif.poe_current else 0,
|
||||||
|
poe_voltage=float(netif.poe_voltage) if netif.poe_voltage else 0,
|
||||||
|
poe_power=float(netif.poe_power) if netif.poe_power else 0,
|
||||||
|
poe_class=netif.poe_class,
|
||||||
|
dot1x_mode=netif.dot1x_mode,
|
||||||
|
dot1x_status=netif.dot1x_status,
|
||||||
|
ip_address=netif.ip
|
||||||
|
) for netif in parse_unifi_nested_dict(section).values()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def discovery_unifi_network_port_if( ## fixme parsed_section_name
|
||||||
|
params: Sequence[Mapping[str, Any]],
|
||||||
|
section: Section,
|
||||||
|
) -> DiscoveryResult:
|
||||||
|
yield from interfaces.discover_interfaces(
|
||||||
|
params,
|
||||||
|
_convert_unifi_counters_if(section),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_unifi_network_port_if( ##fixme parsed_section_name
|
||||||
|
item: str,
|
||||||
|
params: Mapping[str, Any],
|
||||||
|
section: Section,
|
||||||
|
) -> CheckResult:
|
||||||
|
_converted_ifs = _convert_unifi_counters_if(section)
|
||||||
|
iface = next(filter(lambda x: item in (x.index,x.alias),_converted_ifs),None) ## fix Service Discovery appearance alias/descr
|
||||||
|
yield from interfaces.check_multiple_interfaces(
|
||||||
|
item,
|
||||||
|
params,
|
||||||
|
_converted_ifs,
|
||||||
|
)
|
||||||
|
if iface:
|
||||||
|
yield Metric("satisfaction",max(0,iface.satisfaction))
|
||||||
|
#pprint(iface)
|
||||||
|
if iface.poe_enable:
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"PoE: {iface.poe_power}W"
|
||||||
|
)
|
||||||
|
#yield Metric("poe_current",iface.poe_current)
|
||||||
|
yield Metric("poe_power",iface.poe_power)
|
||||||
|
yield Metric("poe_voltage",iface.poe_voltage)
|
||||||
|
if iface.ip_address:
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"IP: {iface.ip_address}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
register.check_plugin(
|
||||||
|
name='unifi_network_ports_if',
|
||||||
|
sections=["unifi_network_ports"],
|
||||||
|
service_name='Interface %s',
|
||||||
|
discovery_ruleset_name="inventory_if_rules",
|
||||||
|
discovery_ruleset_type=register.RuleSetType.ALL,
|
||||||
|
discovery_default_parameters=dict(interfaces.DISCOVERY_DEFAULT_PARAMETERS),
|
||||||
|
discovery_function=discovery_unifi_network_port_if,
|
||||||
|
check_ruleset_name="if",
|
||||||
|
check_default_parameters=interfaces.CHECK_DEFAULT_PARAMETERS,
|
||||||
|
check_function=check_unifi_network_port_if,
|
||||||
|
)
|
||||||
|
############ DEVICERADIO ###########
|
||||||
|
def discovery_unifi_radios(section):
|
||||||
|
#pprint(section)
|
||||||
|
for _radio in section.values():
|
||||||
|
if _radio.radio == "ng":
|
||||||
|
yield Service(item="2.4Ghz")
|
||||||
|
if _radio.radio == "na":
|
||||||
|
yield Service(item="5Ghz")
|
||||||
|
|
||||||
|
def check_unifi_radios(item,section):
|
||||||
|
_item = { "2.4Ghz" : "ng", "5Ghz" : "na" }.get(item)
|
||||||
|
radio = next(filter(lambda x: x.radio == _item,section.values()))
|
||||||
|
yield Metric("read_data",interfaces.saveint(radio.rx_bytes))
|
||||||
|
yield Metric("write_data",interfaces.saveint(radio.tx_bytes))
|
||||||
|
yield Metric("satisfaction",max(0,interfaces.saveint(radio.satisfaction)))
|
||||||
|
yield Metric("wlan_user_sta",interfaces.saveint(radio.user_num_sta))
|
||||||
|
yield Metric("wlan_guest_sta",interfaces.saveint(radio.guest_num_sta))
|
||||||
|
yield Metric("wlan_iot_sta",interfaces.saveint(radio.iot_num_sta))
|
||||||
|
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Channel: {radio.channel}"
|
||||||
|
)
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Satisfaction: {radio.satisfaction}"
|
||||||
|
)
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"User: {radio.num_sta}"
|
||||||
|
)
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Guest: {radio.guest_num_sta}"
|
||||||
|
)
|
||||||
|
|
||||||
|
register.agent_section(
|
||||||
|
name = 'unifi_network_radios',
|
||||||
|
parse_function = parse_unifi_nested_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
register.check_plugin(
|
||||||
|
name='unifi_network_radios',
|
||||||
|
service_name='Radio %s',
|
||||||
|
discovery_function=discovery_unifi_radios,
|
||||||
|
check_function=check_unifi_radios,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############ SSIDs ###########
|
||||||
|
def discovery_unifi_ssids(section):
|
||||||
|
for _ssid in section:
|
||||||
|
yield Service(item=_ssid)
|
||||||
|
|
||||||
|
def check_unifi_ssids(item,section):
|
||||||
|
ssid = section.get(item)
|
||||||
|
_channels = ",".join(list(filter(lambda x: interfaces.saveint(x) > 0,[ssid.ng_channel,ssid.na_channel])))
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Channels: {_channels}"
|
||||||
|
)
|
||||||
|
if (interfaces.saveint(ssid.ng_is_guest) + interfaces.saveint(ssid.na_is_guest)) > 0:
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary="Guest"
|
||||||
|
)
|
||||||
|
_satisfaction = max(0,min(interfaces.saveint(ssid.ng_satisfaction),interfaces.saveint(ssid.na_satisfaction)))
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Satisfaction: {_satisfaction}"
|
||||||
|
)
|
||||||
|
_num_sta = interfaces.saveint(ssid.na_num_sta) + interfaces.saveint(ssid.ng_num_sta)
|
||||||
|
if _num_sta > 0:
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"User: {_num_sta}"
|
||||||
|
)
|
||||||
|
yield Metric("satisfaction",max(0,_satisfaction))
|
||||||
|
yield Metric("wlan_24Ghz_num_user",interfaces.saveint(ssid.ng_num_sta) )
|
||||||
|
yield Metric("wlan_5Ghz_num_user",interfaces.saveint(ssid.na_num_sta) )
|
||||||
|
|
||||||
|
yield Metric("na_avg_client_signal",interfaces.saveint(ssid.na_avg_client_signal))
|
||||||
|
yield Metric("ng_avg_client_signal",interfaces.saveint(ssid.ng_avg_client_signal))
|
||||||
|
|
||||||
|
yield Metric("na_tcp_packet_loss",interfaces.saveint(ssid.na_tcp_packet_loss))
|
||||||
|
yield Metric("ng_tcp_packet_loss",interfaces.saveint(ssid.ng_tcp_packet_loss))
|
||||||
|
|
||||||
|
yield Metric("na_wifi_retries",interfaces.saveint(ssid.na_wifi_retries))
|
||||||
|
yield Metric("ng_wifi_retries",interfaces.saveint(ssid.ng_wifi_retries))
|
||||||
|
yield Metric("na_wifi_latency",interfaces.saveint(ssid.na_wifi_latency))
|
||||||
|
yield Metric("ng_wifi_latency",interfaces.saveint(ssid.ng_wifi_latency))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
register.agent_section(
|
||||||
|
name = 'unifi_network_ssids',
|
||||||
|
parse_function = parse_unifi_nested_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
register.check_plugin(
|
||||||
|
name='unifi_network_ssids',
|
||||||
|
service_name='SSID: %s',
|
||||||
|
discovery_function=discovery_unifi_ssids,
|
||||||
|
check_function=check_unifi_ssids,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############ SSIDsListController ###########
|
||||||
|
def discovery_unifi_ssidlist(section):
|
||||||
|
#pprint(section)
|
||||||
|
for _ssid in section:
|
||||||
|
yield Service(item=_ssid)
|
||||||
|
|
||||||
|
def check_unifi_ssidlist(item,section):
|
||||||
|
ssid = section.get(item)
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"Channels: {ssid.channels}"
|
||||||
|
)
|
||||||
|
# if (interfaces.saveint(ssid.ng_is_guest) + interfaces.saveint(ssid.na_is_guest)) > 0:
|
||||||
|
# yield Result(
|
||||||
|
# state=State.OK,
|
||||||
|
# summary="Guest"
|
||||||
|
# )
|
||||||
|
# yield Result(
|
||||||
|
# state=State.OK,
|
||||||
|
# summary=f"Satisfaction: {_satisfaction}"
|
||||||
|
# )
|
||||||
|
yield Result(
|
||||||
|
state=State.OK,
|
||||||
|
summary=f"User: {ssid.num_sta}"
|
||||||
|
)
|
||||||
|
|
||||||
|
register.agent_section(
|
||||||
|
name = 'unifi_ssid_list',
|
||||||
|
parse_function = parse_unifi_nested_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
register.check_plugin(
|
||||||
|
name='unifi_ssid_list',
|
||||||
|
service_name='SSID: %s',
|
||||||
|
discovery_function=discovery_unifi_ssidlist,
|
||||||
|
check_function=check_unifi_ssidlist,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
480
share/check_mk/agents/special/agent_unifi_controller
Normal file
480
share/check_mk/agents/special/agent_unifi_controller
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||||
|
|
||||||
|
###
|
||||||
|
__VERSION__ = 0.75
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from urllib3.exceptions import InsecureRequestWarning
|
||||||
|
from statistics import mean
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cmk.special_agents.utils.argument_parsing import create_default_argument_parser
|
||||||
|
#from check_api import LOGGER ##/TODO
|
||||||
|
except ImportError:
|
||||||
|
from argparse import ArgumentParser as create_default_argument_parser
|
||||||
|
|
||||||
|
class unifi_api_exception(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class unifi_object(object):
|
||||||
|
def __init__(self,**kwargs):
|
||||||
|
for _k,_v in kwargs.items():
|
||||||
|
_k = _k.replace("-","_")
|
||||||
|
if type(_v) == bool:
|
||||||
|
_v = int(_v)
|
||||||
|
setattr(self,_k,_v)
|
||||||
|
|
||||||
|
self._PARENT = kwargs.get("_PARENT",object)
|
||||||
|
if hasattr(self._PARENT,"_UNIFICONTROLLER"):
|
||||||
|
self._UNIFICONTROLLER = self._PARENT._UNIFICONTROLLER
|
||||||
|
self._API = self._PARENT._API
|
||||||
|
if hasattr(self,"_init"):
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
########################################
|
||||||
|
######
|
||||||
|
###### S S I D
|
||||||
|
######
|
||||||
|
########################################
|
||||||
|
class unifi_network_ssid(unifi_object):
|
||||||
|
def _init(self):
|
||||||
|
self._UNIFICONTROLLER._UNIFI_SSIDS.append(self)
|
||||||
|
self._UNIFI_SITE = self._PARENT._PARENT
|
||||||
|
for _k,_v in getattr(self,"reasons_bar_chart_now",{}).items():
|
||||||
|
setattr(self,_k,_v)
|
||||||
|
def __str__(self):
|
||||||
|
_ret = []
|
||||||
|
_unwanted = ["essid","radio","id","t","name","radio_name","wlanconf_id","is_wep","up","site_id","ap_mac","state"]
|
||||||
|
for _k,_v in self.__dict__.items():
|
||||||
|
if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float):
|
||||||
|
continue
|
||||||
|
_ret.append(f"{self.essid}|{self.radio}_{_k}|{_v}")
|
||||||
|
return "\n".join(_ret)
|
||||||
|
|
||||||
|
########################################
|
||||||
|
######
|
||||||
|
###### R A D I O
|
||||||
|
######
|
||||||
|
########################################
|
||||||
|
class unifi_network_radio(unifi_object):
|
||||||
|
def _update_stats(self,stats):
|
||||||
|
_prefixlen = len(self.name) +1
|
||||||
|
for _k,_v in stats.items():
|
||||||
|
if _k.startswith(self.name):
|
||||||
|
if type(_v) == float:
|
||||||
|
_v = int(_v)
|
||||||
|
setattr(self,_k[_prefixlen:],_v)
|
||||||
|
def __str__(self):
|
||||||
|
_ret = []
|
||||||
|
_unwanted = ["name","ast_be_xmit","extchannel","cu_total","cu_self_rx","cu_self_tx"]
|
||||||
|
for _k,_v in self.__dict__.items():
|
||||||
|
if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float):
|
||||||
|
continue
|
||||||
|
_ret.append(f"{self.name}|{_k}|{_v}")
|
||||||
|
return "\n".join(_ret)
|
||||||
|
|
||||||
|
########################################
|
||||||
|
######
|
||||||
|
###### P O R T
|
||||||
|
######
|
||||||
|
########################################
|
||||||
|
class unifi_network_port(unifi_object):
|
||||||
|
def _init(self):
|
||||||
|
self.oper_status = self._get_state(getattr(self,"up",None))
|
||||||
|
self.admin_status = self._get_state(getattr(self,"enable",None))
|
||||||
|
if hasattr(self,"ifname"): ## GW / UDM Names
|
||||||
|
_name = list(filter(lambda x: x.get("ifname") == self.ifname,self._PARENT.ethernet_overrides))
|
||||||
|
if _name:
|
||||||
|
_name = _name[0]
|
||||||
|
if getattr(self,"name",None) and _name.get("networkgroup") != "LAN":
|
||||||
|
self.name = _name.get("networkgroup","unkn")
|
||||||
|
else:
|
||||||
|
self.name = self.ifname
|
||||||
|
if not hasattr(self,"port_idx") and hasattr(self,"ifname"):
|
||||||
|
self.port_idx = int(self.ifname[-1])+1 ## ethX
|
||||||
|
|
||||||
|
def _get_state(self,state):
|
||||||
|
return {
|
||||||
|
"1" : 1, ## up
|
||||||
|
"0" : 2 ## down
|
||||||
|
}.get(str(state),4) ##unknown
|
||||||
|
def __str__(self):
|
||||||
|
_ret = []
|
||||||
|
_unwanted = ["up","enabled","media","anonymous_id","www_gw_mac","wan_gw_mac","attr_hidden_id","masked","flowctrl_tx","flowctrl_rx","portconf_id","speed_caps"]
|
||||||
|
for _k,_v in self.__dict__.items():
|
||||||
|
if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float):
|
||||||
|
continue
|
||||||
|
_ret.append(f"{self.port_idx}|{_k}|{_v}")
|
||||||
|
return "\n".join(_ret)
|
||||||
|
|
||||||
|
########################################
|
||||||
|
######
|
||||||
|
###### D E V I C E
|
||||||
|
######
|
||||||
|
########################################
|
||||||
|
class unifi_device(unifi_object):
|
||||||
|
def _init(self):
|
||||||
|
if not hasattr(self,"name"):
|
||||||
|
_mac_end = self.mac.replace(":","")[-4:]
|
||||||
|
self.name = f"{self.model}:{_mac_end}"
|
||||||
|
self._piggy_back = True
|
||||||
|
self._PARENT._SITE_DEVICES.append(self)
|
||||||
|
self._NETWORK_PORTS = []
|
||||||
|
self._NETWORK_RADIO = []
|
||||||
|
self._NETWORK_SSIDS = []
|
||||||
|
|
||||||
|
for _k,_v in getattr(self,"sys_stats",{}).items():
|
||||||
|
_k = _k.replace("-","_")
|
||||||
|
setattr(self,_k,_v)
|
||||||
|
if self.type in ("ugw","udm"):
|
||||||
|
## change ip to local ip
|
||||||
|
self.wan_ip = self.ip
|
||||||
|
self.ip = self.connect_request_ip
|
||||||
|
|
||||||
|
if getattr(self,"speedtest_status_saved",False):
|
||||||
|
_speedtest = getattr(self,"speedtest_status",{})
|
||||||
|
self.speedtest_time = int(_speedtest.get("rundate","0"))
|
||||||
|
self.speedtest_status = int(_speedtest.get("status_summary","0"))
|
||||||
|
self.speedtest_ping = round(_speedtest.get("latency",-1),1)
|
||||||
|
self.speedtest_download = round(_speedtest.get("xput_download",0.0),1)
|
||||||
|
self.speedtest_upload = round(_speedtest.get("xput_upload",0.0),1)
|
||||||
|
|
||||||
|
_temp = list(map(lambda x: x.get("value",0),getattr(self,"temperatures",[])))
|
||||||
|
if _temp:
|
||||||
|
self.general_temperature = "{0:.1f}".format(mean(_temp))
|
||||||
|
|
||||||
|
for _port in getattr(self,"port_table",[]):
|
||||||
|
self._NETWORK_PORTS.append(unifi_network_port(_PARENT=self,**_port))
|
||||||
|
|
||||||
|
for _radio in getattr(self,"radio_table_stats",[]):
|
||||||
|
_radio_obj = unifi_network_radio(_PARENT=self,**_radio)
|
||||||
|
_radio_obj._update_stats(getattr(self,"stat",{}).get("ap",{}))
|
||||||
|
self._NETWORK_RADIO.append(_radio_obj)
|
||||||
|
|
||||||
|
for _ssid in getattr(self,"vap_table",[]):
|
||||||
|
self._NETWORK_SSIDS.append(unifi_network_ssid(_PARENT=self,**_ssid))
|
||||||
|
|
||||||
|
def _get_uplink(self):
|
||||||
|
if type(getattr(self,"uplink",None)) == dict:
|
||||||
|
self.uplink_up = int(self.uplink.get("up","0"))
|
||||||
|
self.uplink_device = self._UNIFICONTROLLER._get_device_by_mac(self.uplink.get("uplink_mac"))
|
||||||
|
self.uplink_remote_port = self.uplink.get("uplink_remote_port")
|
||||||
|
self.uplink_type = self.uplink.get("type")
|
||||||
|
|
||||||
|
def _get_short_info(self):
|
||||||
|
_ret = []
|
||||||
|
_wanted = ["version","ip","mac","serial","model","uptime","upgradeable","num_sta"]
|
||||||
|
for _k,_v in self.__dict__.items():
|
||||||
|
if _k.startswith("_") or _k not in _wanted or type(_v) not in (str,int,float):
|
||||||
|
continue
|
||||||
|
_ret.append(f"{self.name}|{_k}|{_v}")
|
||||||
|
return "\n".join(_ret)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self._piggy_back:
|
||||||
|
_piggybackname = getattr(self,self._API.PIGGYBACK_ATTRIBUT,self.name)
|
||||||
|
_ret = [f"<<<<{_piggybackname}>>>>"]
|
||||||
|
else:
|
||||||
|
_ret = []
|
||||||
|
_ret.append("<<<unifi_device:sep(124)>>>")
|
||||||
|
_unwanted = ["anon_id","device_id","site_id","known_cfgversion","cfgversion","syslog_key","has_speaker","has_eth1",
|
||||||
|
"next_interval","next_heartbeat","next_heartbeat_at","guest_token","connect_request_ip","connect_request_port",
|
||||||
|
"start_connected_millis","start_disconnected_millis","wlangroup_id_na","wlangroup_id_ng","uplink_down_timeout"
|
||||||
|
"unsupported_reason","connected_at","provisioned_at","fw_caps","hw_caps","manufacturer_id","use_custom_config",
|
||||||
|
"led_override","led_override_color","led_override_color_brightness","sys_error_caps","adoptable_when_upgraded",
|
||||||
|
"mesh_uplink_1","mesh_uplink_1","considered_lost_at","outdoor_mode_override","unsupported_reason","architecture",
|
||||||
|
"kernel_version","required_version","prev_non_busy_state","has_fan","has_temperature","flowctrl_enabled","hash_id",
|
||||||
|
"speedtest-status-saved","usg_caps","two_phase_adopt","rollupgrade","locating","dot1x_portctrl_enabled",
|
||||||
|
"lcm_idle_timeout_override","lcm_brightness_override","uplink_depth","mesh_sta_vap_enabled","mesh_uplink_2",
|
||||||
|
"lcm_tracker_enabled","model_incompatible","model_in_lts","model_in_eol","country_code","wifi_caps",
|
||||||
|
"meshv3_peer_mac","element_peer_mac","vwireEnabled","hide_ch_width","x_authkey","x_ssh_hostkey_fingerprint",
|
||||||
|
"x_fingerprint","x_inform_authkey","op_mode"
|
||||||
|
]
|
||||||
|
for _k,_v in self.__dict__.items():
|
||||||
|
if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float):
|
||||||
|
continue
|
||||||
|
_ret.append(f"{_k}|{_v}")
|
||||||
|
|
||||||
|
_ret.append("<<<labels:sep(0)>>>")
|
||||||
|
_ret.append(f"{{\"unifi_device\":\"unifi-{self.type}\"}}")
|
||||||
|
|
||||||
|
if self._NETWORK_PORTS:
|
||||||
|
_ret += ["","<<<unifi_network_ports:sep(124)>>>"] + [str(_port) for _port in self._NETWORK_PORTS]
|
||||||
|
if self._NETWORK_RADIO:
|
||||||
|
_ret += ["","<<<unifi_network_radios:sep(124)>>>"] + [str(_radio) for _radio in self._NETWORK_RADIO]
|
||||||
|
|
||||||
|
if self._NETWORK_SSIDS:
|
||||||
|
_ret += ["","<<<unifi_network_ssids:sep(124)>>>"] + [str(_ssid) for _ssid in sorted(self._NETWORK_SSIDS,key=lambda x: x.essid)]
|
||||||
|
return "\n".join(_ret)
|
||||||
|
|
||||||
|
########################################
|
||||||
|
######
|
||||||
|
###### S I T E
|
||||||
|
######
|
||||||
|
########################################
|
||||||
|
class unifi_site(unifi_object):
|
||||||
|
def _init(self):
|
||||||
|
for _subsys in self.health:
|
||||||
|
_name = _subsys.get("subsystem")
|
||||||
|
for _k,_v in _subsys.items():
|
||||||
|
_k = _k.replace("-","_")
|
||||||
|
if _k == "subsystem" or type(_v) not in (str,int,float):
|
||||||
|
continue
|
||||||
|
#print(f"{_k}:{_v}")
|
||||||
|
setattr(self,f"{_name}_{_k}",_v)
|
||||||
|
|
||||||
|
##pprint(_api.get_data("/stat/rogueap"))
|
||||||
|
self._SITE_DEVICES = []
|
||||||
|
self._get_devices()
|
||||||
|
_satisfaction = list(filter(
|
||||||
|
lambda x: x != None,map(
|
||||||
|
lambda x: getattr(x,"satisfaction",None),self._SITE_DEVICES
|
||||||
|
)
|
||||||
|
))
|
||||||
|
self.satisfaction = max(0,int(mean(_satisfaction)) if _satisfaction else 0)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_devices(self):
|
||||||
|
_data = self._API.get_devices(site=self.name)
|
||||||
|
for _device in _data:
|
||||||
|
self._UNIFICONTROLLER._UNIFI_DEVICES.append(unifi_device(_PARENT=self,**_device))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
_ret = ["<<<unifi_sites:sep(124)>>>"]
|
||||||
|
_unwanted = ["name","anonymous_id","www_gw_mac","wan_gw_mac","attr_hidden_id","attr_no_delete",""]
|
||||||
|
for _k,_v in self.__dict__.items():
|
||||||
|
if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float):
|
||||||
|
continue
|
||||||
|
_ret.append(f"{self.name}|{_k}|{_v}")
|
||||||
|
return "\n".join(_ret)
|
||||||
|
|
||||||
|
########################################
|
||||||
|
######
|
||||||
|
###### C O N T R O L L E R
|
||||||
|
######
|
||||||
|
########################################
|
||||||
|
class unifi_controller(unifi_object):
|
||||||
|
def _init(self):
|
||||||
|
self._UNIFICONTROLLER = self
|
||||||
|
self._UNIFI_SITES = []
|
||||||
|
self._UNIFI_DEVICES = []
|
||||||
|
self._UNIFI_SSIDS = []
|
||||||
|
self._get_systemhealth()
|
||||||
|
self._get_sites()
|
||||||
|
for _dev in self._UNIFI_DEVICES:
|
||||||
|
_dev._get_uplink()
|
||||||
|
if hasattr(self,"cloudkey_version"):
|
||||||
|
self.cloudkey_version = re.sub(".*?v(\d+\.\d+\.\d+\.[a-z0-9]+).*","\\1",self.cloudkey_version)
|
||||||
|
self.type = getattr(self,"ubnt_device_type","unifi-sw-controller")
|
||||||
|
self.controller_version = self.version
|
||||||
|
delattr(self,"version")
|
||||||
|
|
||||||
|
def _get_systemhealth(self):
|
||||||
|
_data = self._API.get_sysinfo()
|
||||||
|
_wanted = ["timezone","autobackup","version","previous_version","update_available","hostname","name","uptime","cloudkey_update_available","cloudkey_update_version","cloudkey_version","ubnt_device_type","udm_version","udm_update_version","udm_update_available"]
|
||||||
|
if _data:
|
||||||
|
for _k,_v in _data[0].items():
|
||||||
|
if _k in _wanted:
|
||||||
|
if type(_v) == bool:
|
||||||
|
_v = int(_v)
|
||||||
|
setattr(self,_k,_v)
|
||||||
|
|
||||||
|
def _get_device_by_mac(self,mac):
|
||||||
|
try:
|
||||||
|
return next(filter(lambda x: x.mac == mac,self._UNIFI_DEVICES)).name
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_sites(self):
|
||||||
|
_data = self._API.get_sites()
|
||||||
|
for _site in _data:
|
||||||
|
if self._API.SITES and _site.get("name") not in self._API.SITES and _site.get("desc").lower() not in self._API.SITES:
|
||||||
|
continue
|
||||||
|
self._UNIFI_SITES.append(unifi_site(_PARENT=self,**_site))
|
||||||
|
|
||||||
|
def _get_ssidlist(self):
|
||||||
|
_dict = defaultdict(list)
|
||||||
|
for _ssid in self._UNIFI_SSIDS:
|
||||||
|
_dict[f"{_ssid.essid}@{_ssid._UNIFI_SITE.desc}"].append(_ssid)
|
||||||
|
|
||||||
|
_ret = []
|
||||||
|
for _ssid,_obj in _dict.items():
|
||||||
|
_ret.append("|".join([_ssid,"num_sta",str(sum(map(lambda x: getattr(x,"num_sta",0),_obj)))]))
|
||||||
|
_ret.append("|".join([_ssid,"channels",",".join(
|
||||||
|
sorted(
|
||||||
|
set(map(lambda x: str(getattr(x,"channel","0")),_obj))
|
||||||
|
,key = lambda x: int(x))
|
||||||
|
)]))
|
||||||
|
_ret.append("|".join([_ssid,"avg_client_signal",str(mean(map(lambda x: getattr(x,"avg_client_signal",0),_obj))) ]))
|
||||||
|
return _ret
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
_ret = ["<<<unifi_controller:sep(124)>>>"]
|
||||||
|
for _k,_v in self.__dict__.items():
|
||||||
|
if _k.startswith("_") or type(_v) not in (str,int,float):
|
||||||
|
continue
|
||||||
|
_ret.append(f"{_k}|{_v}")
|
||||||
|
|
||||||
|
## check udm
|
||||||
|
_has_udm = list(filter(lambda x: x.name == self.name,self._UNIFI_DEVICES))
|
||||||
|
if _has_udm:
|
||||||
|
_udm = _has_udm[0]
|
||||||
|
_udm._piggy_back = False
|
||||||
|
_ret.append(str(_udm))
|
||||||
|
|
||||||
|
_ret.append("<<<labels:sep(0)>>>")
|
||||||
|
_ret.append(f"{{\"unifi_device\":\"unifi-{self.type}\"}}")
|
||||||
|
|
||||||
|
## SITES ##
|
||||||
|
for _site in self._UNIFI_SITES:
|
||||||
|
_ret.append(str(_site))
|
||||||
|
|
||||||
|
_ret.append("<<<unifi_device_shortlist>>>")
|
||||||
|
for _device in self._UNIFI_DEVICES:
|
||||||
|
if _device._piggy_back:
|
||||||
|
_ret.append(_device._get_short_info())
|
||||||
|
## device list
|
||||||
|
|
||||||
|
## ssid list
|
||||||
|
_ret.append("<<<unifi_ssid_list:sep(124)>>>")
|
||||||
|
_ret += self._get_ssidlist()
|
||||||
|
|
||||||
|
if self._API.PIGGYBACK_ATTRIBUT.lower() != "none":
|
||||||
|
## PIGGYBACK DEVICES ##
|
||||||
|
for _device in self._UNIFI_DEVICES:
|
||||||
|
if _device._piggy_back:
|
||||||
|
_ret.append(str(_device))
|
||||||
|
return "\n".join(_ret)
|
||||||
|
|
||||||
|
|
||||||
|
########################################
|
||||||
|
######
|
||||||
|
###### A P I
|
||||||
|
###### https://ubntwiki.com/products/software/unifi-controller/api
|
||||||
|
########################################
|
||||||
|
class unifi_controller_api(object):
|
||||||
|
def __init__(self,host,username,password,port,site,verify_cert,rawapi,piggybackattr,**kwargs):
|
||||||
|
self.host = host
|
||||||
|
self.url = f"https://{host}"
|
||||||
|
if port != 443:
|
||||||
|
self.url = f"https://{host}:{port}"
|
||||||
|
|
||||||
|
self._verify_cert = verify_cert
|
||||||
|
if not verify_cert:
|
||||||
|
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
||||||
|
self.RAW_API = rawapi
|
||||||
|
self.PIGGYBACK_ATTRIBUT = piggybackattr
|
||||||
|
self.SITES = site.lower().split(",") if site else None
|
||||||
|
self._session = requests.Session()
|
||||||
|
self.check_unifi_os()
|
||||||
|
self.login(username,password)
|
||||||
|
|
||||||
|
def check_unifi_os(self):
|
||||||
|
_response = self.request("GET",url=self.url,allow_redirects=False)
|
||||||
|
self.is_unifios= _response.status_code == 200 and _response.headers.get("x-csrf-token")
|
||||||
|
|
||||||
|
def get_sysinfo(self):
|
||||||
|
return self.get_data("/stat/sysinfo")
|
||||||
|
|
||||||
|
def get_sites(self):
|
||||||
|
return self.get_data("/stat/sites",site=None)
|
||||||
|
|
||||||
|
def get_devices(self,site):
|
||||||
|
return self.get_data("/stat/device",site=site)
|
||||||
|
|
||||||
|
def login(self,username,password):
|
||||||
|
if self.is_unifios:
|
||||||
|
url=f"{self.url}/api/auth/login"
|
||||||
|
else:
|
||||||
|
url=f"{self.url}/api/login"
|
||||||
|
auth = {
|
||||||
|
"username" : username,
|
||||||
|
"password" : password,
|
||||||
|
"remember" : True
|
||||||
|
}
|
||||||
|
_response = self.request("POST",url=url,json=auth)
|
||||||
|
if _response.status_code == 404:
|
||||||
|
raise unifi_api_exception("API not Found try other Port or IP")
|
||||||
|
_json = _response.json()
|
||||||
|
if _json.get("meta",{}).get("rc") == "ok" or _json.get("status") == "ACTIVE":
|
||||||
|
return
|
||||||
|
raise unifi_api_exception("Login failed")
|
||||||
|
|
||||||
|
def get_data(self,path,site="default",method="GET"):
|
||||||
|
_json = self.request(method=method,path=path,site=site).json()
|
||||||
|
_meta = _json.get("meta",{})
|
||||||
|
if _meta.get("rc") == "ok":
|
||||||
|
return _json.get("data",[])
|
||||||
|
raise unifi_api_exception(_meta.get("msg",_json.get("errors",repr(_json))))
|
||||||
|
|
||||||
|
def request(self,method,url=None,path=None,site=None,json=None,**kwargs):
|
||||||
|
if not url:
|
||||||
|
if self.is_unifios:
|
||||||
|
url = f"{self.url}/proxy/network/api/"
|
||||||
|
else:
|
||||||
|
url = f"{self.url}/api"
|
||||||
|
if site is not None:
|
||||||
|
url += f"/s/{site}"
|
||||||
|
if path is not None:
|
||||||
|
url += f"{path}"
|
||||||
|
_request = requests.Request(method,url,json=json)
|
||||||
|
_prepped_request = self._session.prepare_request(_request)
|
||||||
|
else:
|
||||||
|
_request = requests.Request(method,url,json=json)
|
||||||
|
_prepped_request = _request.prepare()
|
||||||
|
_response = self._session.send(_prepped_request,verify=self._verify_cert,timeout=10,**kwargs)
|
||||||
|
if _response.status_code == 200 and hasattr(_response,"json") and self.RAW_API:
|
||||||
|
try:
|
||||||
|
pprint(_response.json())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return _response
|
||||||
|
|
||||||
|
########################################
|
||||||
|
######
|
||||||
|
###### M A I N
|
||||||
|
######
|
||||||
|
########################################
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = create_default_argument_parser(description=__doc__)
|
||||||
|
parser.add_argument('-u', '--user', dest='username', required=True,
|
||||||
|
help='User to access the DSM.')
|
||||||
|
parser.add_argument('-p', '--password', dest='password', required=True,
|
||||||
|
help='Password to access the DSM.')
|
||||||
|
parser.add_argument('--ignore-cert', dest='verify_cert', action='store_false',
|
||||||
|
help='Do not verify the SSL cert')
|
||||||
|
parser.add_argument('-s','--site', dest='site', required=False,
|
||||||
|
help='Site')
|
||||||
|
parser.add_argument('--port', dest='port',type=int,default='443')
|
||||||
|
parser.add_argument('--piggyback', dest='piggybackattr',type=str,default='name')
|
||||||
|
parser.add_argument('--rawapi', dest='rawapi', action='store_true')
|
||||||
|
parser.add_argument("host",type=str,
|
||||||
|
help="""Host name or IP address of Unifi Controller""")
|
||||||
|
args = parser.parse_args()
|
||||||
|
print("<<<check_mk>>>")
|
||||||
|
print(f"Version: {__VERSION__}")
|
||||||
|
try:
|
||||||
|
_api = unifi_controller_api(**args.__dict__)
|
||||||
|
except socket.error as e:
|
||||||
|
pprint(e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if _api.is_unifios:
|
||||||
|
print("AgentOS: UnifiOS")
|
||||||
|
#pprint(_api.get_data("rest/portconf",site="default",method="GET"))
|
||||||
|
##pprint(_api.get_data("/stat/rogueap"))
|
||||||
|
##pprint(_api.get_data("/rest/user",site="default",method="GET"))
|
||||||
|
##pprint(_api.get_data("/stat/sta",site="default",method="GET"))
|
||||||
|
#sys.exit(0)
|
||||||
|
_controller = unifi_controller(_API=_api)
|
||||||
|
if args.rawapi == False:
|
||||||
|
print(_controller)
|
26
share/check_mk/checks/agent_unifi_controller
Normal file
26
share/check_mk/checks/agent_unifi_controller
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||||
|
|
||||||
|
#Function get params (in this case is port, passed via WATO agent rule cunfiguration, hostname and ip addres of host,
|
||||||
|
#for which agent will be invoked
|
||||||
|
def agent_unifi_controller_arguments(params, hostname, ipaddress):
|
||||||
|
args = [
|
||||||
|
'--user', params['user'],
|
||||||
|
'--password', passwordstore_get_cmdline('%s', params['password']),
|
||||||
|
'--port', params['port'],
|
||||||
|
'--piggyback',params['piggyback'],
|
||||||
|
]
|
||||||
|
_site = params.get("site")
|
||||||
|
if _site:
|
||||||
|
args += ["--site",_site]
|
||||||
|
if 'ignore_cert' in params and params['ignore_cert'] != '':
|
||||||
|
args += ['--ignore-cert']
|
||||||
|
args += [ipaddress]
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
#register invoke function for our agent
|
||||||
|
#key value for this dictionary is name part from register datasource of our agent (name="special_agents:myspecial")
|
||||||
|
special_agent_info['unifi_controller'] = agent_unifi_controller_arguments
|
||||||
|
|
||||||
|
|
76
share/check_mk/inventory/unifi_controller
Normal file
76
share/check_mk/inventory/unifi_controller
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
class dictobject(defaultdict):
|
||||||
|
def __getattr__(self,name):
|
||||||
|
return self[name] if name in self else ""
|
||||||
|
|
||||||
|
nested_dictobject = lambda: dictobject(nested_dictobject)
|
||||||
|
|
||||||
|
def inv_unifi_controller(info):
|
||||||
|
node = inv_tree("software.os")
|
||||||
|
node["version"] = info.get("controller_version")
|
||||||
|
|
||||||
|
def inv_unifi_device(info):
|
||||||
|
node = inv_tree("software.configuration.snmp_info")
|
||||||
|
node["name"] = info.get("name")
|
||||||
|
node["contact"] = info.get("snmp_contact")
|
||||||
|
node["location"] = info.get("snmp_location")
|
||||||
|
node = inv_tree("software.os")
|
||||||
|
node["version"] = info.get("version")
|
||||||
|
node = inv_tree("harware.system")
|
||||||
|
node["vendor"] = "ubiquiti"
|
||||||
|
for _key in ("model","board_rev","serial","mac"):
|
||||||
|
_val = info.get(_key)
|
||||||
|
if _val:
|
||||||
|
node[_key] = _val
|
||||||
|
|
||||||
|
def inv_unifi_port(info,params,inventory_tree):
|
||||||
|
_parsed = nested_dictobject()
|
||||||
|
for _line in info:
|
||||||
|
_parsed[_line[0]][_line[1]] = _line[2]
|
||||||
|
|
||||||
|
_interfaces = []
|
||||||
|
_total_ethernet_ports = 0
|
||||||
|
_available_ethernet_ports = 0
|
||||||
|
def _saveint(num):
|
||||||
|
try:
|
||||||
|
return int(num)
|
||||||
|
except (TypeError,ValueError):
|
||||||
|
return 0
|
||||||
|
for _iface in _parsed.values():
|
||||||
|
_interfaces.append({
|
||||||
|
"index" : int(_iface.port_idx),
|
||||||
|
"description" : _iface.name,
|
||||||
|
"alias" : _iface.name,
|
||||||
|
"speed" : _saveint(_iface.speed)*1000000,
|
||||||
|
"phys_address" : "",
|
||||||
|
"oper_status" : _saveint(_iface.oper_status),
|
||||||
|
"admin_status" : _saveint(_iface.admin_status),
|
||||||
|
"port_type" : 6,
|
||||||
|
"available" : _iface.oper_status == '2'
|
||||||
|
})
|
||||||
|
_total_ethernet_ports+=1
|
||||||
|
_available_ethernet_ports+=1 if _iface.oper_status == '2' else 0
|
||||||
|
|
||||||
|
node = inventory_tree.get_list("networking.interfaces:")
|
||||||
|
node.extend(sorted(_interfaces, key=lambda i: i.get('index')))
|
||||||
|
node = inventory_tree.get_dict("networking.")
|
||||||
|
node["available_ethernet_ports"] = _available_ethernet_ports
|
||||||
|
node["total_ethernet_ports"] = _total_ethernet_ports
|
||||||
|
node["total_interfaces"] = len(_parsed)
|
||||||
|
|
||||||
|
inv_info["unifi_controller"] = {
|
||||||
|
"inv_function" : inv_unifi_controller
|
||||||
|
}
|
||||||
|
inv_info["unifi_device"] = {
|
||||||
|
"inv_function" : inv_unifi_device
|
||||||
|
}
|
||||||
|
|
||||||
|
inv_info["unifi_network_ports"] = {
|
||||||
|
"inv_function" : inv_unifi_port
|
||||||
|
}
|
||||||
|
|
209
share/check_mk/web/plugins/metrics/unifi_metrics.py
Normal file
209
share/check_mk/web/plugins/metrics/unifi_metrics.py
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
from cmk.gui.i18n import _
|
||||||
|
from cmk.gui.plugins.metrics import (
|
||||||
|
metric_info,
|
||||||
|
graph_info,
|
||||||
|
translation
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Colors:
|
||||||
|
#
|
||||||
|
# red
|
||||||
|
# magenta orange
|
||||||
|
# 11 12 13 14 15 16
|
||||||
|
# 46 21
|
||||||
|
# 45 22
|
||||||
|
# blue 44 23 yellow
|
||||||
|
# 43 24
|
||||||
|
# 42 25
|
||||||
|
# 41 26
|
||||||
|
# 36 35 34 33 32 31
|
||||||
|
# cyan yellow-green
|
||||||
|
# green
|
||||||
|
#
|
||||||
|
# Special colors:
|
||||||
|
# 51 gray
|
||||||
|
# 52 brown 1
|
||||||
|
# 53 brown 2
|
||||||
|
#
|
||||||
|
# For a new metric_info you have to choose a color. No more hex-codes are needed!
|
||||||
|
# Instead you can choose a number of the above color ring and a letter 'a' or 'b
|
||||||
|
# where 'a' represents the basic color and 'b' is a nuance/shading of the basic color.
|
||||||
|
# Both number and letter must be declared!
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# "color" : "23/a" (basic color yellow)
|
||||||
|
# "color" : "23/b" (nuance of color yellow)
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
metric_info["satisfaction"] = {
|
||||||
|
"title": _("Satisfaction"),
|
||||||
|
"unit": "%",
|
||||||
|
"color": "16/a",
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_info["poe_current"] = {
|
||||||
|
"title": _("PoE Current"),
|
||||||
|
"unit": "a",
|
||||||
|
"color": "16/a",
|
||||||
|
}
|
||||||
|
metric_info["poe_voltage"] = {
|
||||||
|
"title": _("PoE Voltage"),
|
||||||
|
"unit": "v",
|
||||||
|
"color": "12/a",
|
||||||
|
}
|
||||||
|
metric_info["poe_power"] = {
|
||||||
|
"title": _("PoE Power"),
|
||||||
|
"unit": "w",
|
||||||
|
"color": "16/a",
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_info["user_sta"] = {
|
||||||
|
"title": _("User"),
|
||||||
|
"unit": "",
|
||||||
|
"color": "13/b",
|
||||||
|
}
|
||||||
|
metric_info["guest_sta"] = {
|
||||||
|
"title": _("Guest"),
|
||||||
|
"unit": "",
|
||||||
|
"color": "13/a",
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_info["user_sta_combined"] = {
|
||||||
|
"title" : _("User"),
|
||||||
|
"metrics" : [
|
||||||
|
("user_sta","area"),
|
||||||
|
("guest_sta","stack"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_info["lan_user_sta"] = {
|
||||||
|
"title": _("LAN User"),
|
||||||
|
"unit": "count",
|
||||||
|
"color": "13/b",
|
||||||
|
}
|
||||||
|
metric_info["lan_guest_sta"] = {
|
||||||
|
"title": _("LAN Guest"),
|
||||||
|
"unit": "count",
|
||||||
|
"color": "13/a",
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_info["lan_user_sta_combined"] = {
|
||||||
|
"title" : _("LAN-User"),
|
||||||
|
"metrics" : [
|
||||||
|
("lan_user_sta","area"),
|
||||||
|
("lan_guest_sta","stack"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_info["wlan_user_sta"] = {
|
||||||
|
"title": _("WLAN User"),
|
||||||
|
"unit": "count",
|
||||||
|
"color": "13/b",
|
||||||
|
}
|
||||||
|
metric_info["wlan_guest_sta"] = {
|
||||||
|
"title": _("WLAN Guest"),
|
||||||
|
"unit": "count",
|
||||||
|
"color": "13/a",
|
||||||
|
}
|
||||||
|
metric_info["wlan_iot_sta"] = {
|
||||||
|
"title": _("WLAN IoT Devices"),
|
||||||
|
"unit": "count",
|
||||||
|
"color": "14/a",
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_info["wlan_user_sta_combined"] = {
|
||||||
|
"title" : _("WLAN-User"),
|
||||||
|
"metrics" : [
|
||||||
|
("wlan_user_sta","area"),
|
||||||
|
("wlan_guest_sta","stack"),
|
||||||
|
("wlan_iot_sta","stack"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_info["wlan_24Ghz_num_user"] = {
|
||||||
|
"title": _("User 2.4Ghz"),
|
||||||
|
"unit": "count",
|
||||||
|
"color": "13/b",
|
||||||
|
}
|
||||||
|
metric_info["wlan_5Ghz_num_user"] = {
|
||||||
|
"title": _("User 5Ghz"),
|
||||||
|
"unit": "count",
|
||||||
|
"color": "13/a",
|
||||||
|
}
|
||||||
|
|
||||||
|
graph_info["wlan_user_band_combined"] = {
|
||||||
|
"title" : _("WLAN User"),
|
||||||
|
"metrics" : [
|
||||||
|
("wlan_24Ghz_num_user","area"),
|
||||||
|
("wlan_5Ghz_num_user","stack"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
#na_avg_client_signal
|
||||||
|
#ng_avg_client_signal
|
||||||
|
|
||||||
|
|
||||||
|
metric_info["wlan_if_in_octets"] = {
|
||||||
|
"title": _("Input Octets"),
|
||||||
|
"unit": "bytes/s",
|
||||||
|
"color": "#00e060",
|
||||||
|
}
|
||||||
|
metric_info["wlan_if_out_octets"] = {
|
||||||
|
"title": _("Output Octets"),
|
||||||
|
"unit": "bytes/s",
|
||||||
|
"color": "#00e060",
|
||||||
|
}
|
||||||
|
graph_info["wlan_bandwidth_translated"] = {
|
||||||
|
"title": _("Bandwidth WLAN"),
|
||||||
|
"metrics": [
|
||||||
|
("wlan_if_in_octets,8,*@bits/s", "area", _("Input bandwidth")),
|
||||||
|
("wlan_if_out_octets,8,*@bits/s", "-area", _("Output bandwidth")),
|
||||||
|
],
|
||||||
|
"scalars": [
|
||||||
|
("if_in_octets:warn", _("Warning (In)")),
|
||||||
|
("if_in_octets:crit", _("Critical (In)")),
|
||||||
|
("if_out_octets:warn,-1,*", _("Warning (Out)")),
|
||||||
|
("if_out_octets:crit,-1,*", _("Critical (Out)")),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
metric_info["na_avg_client_signal"] = {
|
||||||
|
"title" :_("Average Signal 5Ghz"),
|
||||||
|
"unit" : "db",
|
||||||
|
"color" : "14/a",
|
||||||
|
}
|
||||||
|
metric_info["ng_avg_client_signal"] = {
|
||||||
|
"title" :_("Average Signal 2.4Ghz"),
|
||||||
|
"unit" : "db",
|
||||||
|
"color" : "#80f000",
|
||||||
|
}
|
||||||
|
graph_info["avg_client_signal_combined"] = {
|
||||||
|
"title" : _("Average Client Signal"),
|
||||||
|
"metrics" : [
|
||||||
|
("na_avg_client_signal","line"),
|
||||||
|
("ng_avg_client_signal","line"),
|
||||||
|
],
|
||||||
|
"range" : (-100,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## different unit ???
|
||||||
|
#graph_info["poe_usage_combined"] = {
|
||||||
|
# "title" : _("PoE Usage"),
|
||||||
|
# "metrics" : [
|
||||||
|
# ("poe_power","area"),
|
||||||
|
# ("poe_voltage","line"),
|
||||||
|
# ],
|
||||||
|
#}
|
||||||
|
|
||||||
|
### fixme default uptime translation?
|
||||||
|
metric_info["unifi_uptime"] = {
|
||||||
|
"title" :_("Uptime"),
|
||||||
|
"unit" : "s",
|
||||||
|
"color" : "#80f000",
|
||||||
|
}
|
||||||
|
|
||||||
|
check_metrics["check_mk-unifi_network_ports_if"] = translation.if_translation
|
15
share/check_mk/web/plugins/perfometer/unifi_performeter.py
Normal file
15
share/check_mk/web/plugins/perfometer/unifi_performeter.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from cmk.gui.plugins.metrics import perfometer_info
|
||||||
|
|
||||||
|
perfometer_info.append({
|
||||||
|
"type": "linear",
|
||||||
|
"segments": ["satisfaction"],
|
||||||
|
"total": 100.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
perfometer_info.append({
|
||||||
|
"type": "logarithmic",
|
||||||
|
"metric": "unifi_uptime",
|
||||||
|
"half_value": 2592000.0,
|
||||||
|
"exponent": 2,
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- encoding: utf-8; py-indent-offset: 4 -*-
|
||||||
|
|
||||||
|
from cmk.gui.i18n import _
|
||||||
|
from cmk.gui.plugins.wato import (
|
||||||
|
HostRulespec,
|
||||||
|
IndividualOrStoredPassword,
|
||||||
|
rulespec_registry,
|
||||||
|
)
|
||||||
|
from cmk.gui.valuespec import (
|
||||||
|
Dictionary,
|
||||||
|
Alternative,
|
||||||
|
NetworkPort,
|
||||||
|
Checkbox,
|
||||||
|
TextAscii,
|
||||||
|
)
|
||||||
|
|
||||||
|
from cmk.gui.plugins.wato.datasource_programs import RulespecGroupDatasourceProgramsHardware
|
||||||
|
|
||||||
|
def _valuespec_special_agent_unifi_controller():
|
||||||
|
return Dictionary(
|
||||||
|
title = _('Unifi Controller via API'),
|
||||||
|
help = _('This rule selects the Unifi API agent'),
|
||||||
|
optional_keys=['site'],
|
||||||
|
elements=[
|
||||||
|
('user',TextAscii(title = _('API Username.'),allow_empty = False,)),
|
||||||
|
('password',IndividualOrStoredPassword(title = _('API password'),allow_empty = False,)),
|
||||||
|
('site',TextAscii(title = _('Site Name'),allow_empty = False,default_value='default')), ## optional but not empty
|
||||||
|
('port',NetworkPort(title = _('Port'),default_value = 443)),
|
||||||
|
('ignore_cert', Checkbox(title=_("Ignore certificate validation"), default_value=False)),
|
||||||
|
('piggyback',
|
||||||
|
Alternative(
|
||||||
|
title = _('Receive piggyback data by'),
|
||||||
|
elements = [
|
||||||
|
FixedValue("name", title = _("Hostname")),
|
||||||
|
FixedValue("ip", title = _("IP")),
|
||||||
|
FixedValue("none", title = _("None")),
|
||||||
|
],
|
||||||
|
default_value = "name"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
rulespec_registry.register(
|
||||||
|
HostRulespec(
|
||||||
|
group=RulespecGroupDatasourceProgramsHardware,
|
||||||
|
#IMPORTANT, name must follow special_agents:<name>,
|
||||||
|
#where filename of our special agent located in path local/share/check_mk/agents/special/ is agent_<name>
|
||||||
|
name='special_agents:unifi_controller',
|
||||||
|
valuespec=_valuespec_special_agent_unifi_controller,
|
||||||
|
))
|
Loading…
Reference in New Issue
Block a user