├── LICENSE ├── README.md ├── lib └── check_mk │ └── base │ └── plugins │ └── agent_based │ └── unifi_controller.py └── share └── check_mk ├── agents └── special │ └── agent_unifi_controller ├── checkman ├── unifi_controller ├── unifi_device ├── unifi_device_shortlist ├── unifi_network_ports_if ├── unifi_network_radios ├── unifi_network_ssids ├── unifi_sites └── unifi_ssid_list ├── checks └── agent_unifi_controller ├── inventory └── unifi_controller └── web └── plugins ├── metrics └── unifi_metrics.py ├── perfometer └── unifi_performeter.py └── wato └── datasource_unifi_controller.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bash Club 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # check-unifi-controller 2 | Checkmk special agent for checking unifi controller 3 | 4 | ### Usage: 5 | Login into your checkmk instnace user on your checkmk server (e.g. via SSH). 6 | Download and install the checkmk agent: 7 | ~~~ 8 | wget https://github.com/bashclub/check-unifi-controller/releases/download/v0.87/unifi_controller-0.87.mkp 9 | mkp install ./unifi_controller-0.87.mkp 10 | ~~~ 11 | 12 | ### Configure Agent 13 | Login into your checkmk website and go to "Setup" -> "Agents" -> "Other integrations" (Datasource programs). Under the category "Hardware" click on "Unifi Controller via API" and create a new rule. 14 | Fill in the credentials of your Unifi controller, set the HTTPS Port, define the site name (if other than default), check "Ignore certificate validation" if using a self signed certificate, select Hostname or IP for storing piggyback data. 15 | Under "Conditions" assign an "Explicit host" with your Unifi Controller Machine. 16 | The agent will carry piggyback data for switches and access points and you can create new hosts to monitor, where piggyback data will be assignesd on exact match (IP or hostname). 17 | -------------------------------------------------------------------------------- /lib/check_mk/base/plugins/agent_based/unifi_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf-8; py-indent-offset: 4 -*- 3 | # 4 | ## MIT License 5 | ## 6 | ## Copyright (c) 2024 Bash Club 7 | ## 8 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 9 | ## of this software and associated documentation files (the "Software"), to deal 10 | ## in the Software without restriction, including without limitation the rights 11 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | ## copies of the Software, and to permit persons to whom the Software is 13 | ## furnished to do so, subject to the following conditions: 14 | ## 15 | ## The above copyright notice and this permission notice shall be included in all 16 | ## copies or substantial portions of the Software. 17 | ## 18 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | ## SOFTWARE. 25 | 26 | from cmk.gui.i18n import _ 27 | 28 | from .agent_based_api.v1 import ( 29 | Metric, 30 | register, 31 | render, 32 | Result, 33 | IgnoreResults, 34 | Service, 35 | State, 36 | TableRow, 37 | Attributes 38 | ) 39 | 40 | from .agent_based_api.v1.type_defs import CheckResult, DiscoveryResult 41 | from typing import Any, Dict, Mapping, Sequence, Optional 42 | 43 | from dataclasses import dataclass 44 | from collections import defaultdict 45 | from .utils import interfaces 46 | 47 | SubSection = Dict[str,str] 48 | Section = Dict[str, SubSection] 49 | class dictobject(defaultdict): 50 | def __getattr__(self,name): 51 | return self[name] if name in self else "" 52 | 53 | nested_dictobject = lambda: dictobject(nested_dictobject) 54 | 55 | def _expect_bool(val,expected=True,failstate=State.WARN): 56 | return State.OK if bool(int(val)) == expected else failstate 57 | 58 | def _expect_number(val,expected=0,failstate=State.WARN): 59 | return State.OK if int(val) == expected else failstate 60 | 61 | def _safe_float(val): 62 | try: 63 | return float(val) 64 | except (TypeError,ValueError): 65 | return 0 66 | 67 | def _safe_int(val,default=0): 68 | try: 69 | return int(val) 70 | except (TypeError,ValueError): 71 | return default 72 | 73 | def _unifi_status2state(status): 74 | return { 75 | "ok" : State.OK, 76 | "warning" : State.WARN, 77 | "error" : State.CRIT 78 | }.get(status.lower(),State.UNKNOWN) 79 | 80 | from pprint import pprint 81 | 82 | def parse_unifi_dict(string_table): 83 | _ret = dictobject() 84 | for _line in string_table: 85 | _ret[_line[0]] = _line[1] 86 | return _ret 87 | 88 | def parse_unifi_nested_dict(string_table): 89 | _ret = nested_dictobject() 90 | for _line in string_table: 91 | _ret[_line[0]][_line[1]] = _line[2] 92 | return _ret 93 | 94 | UNIFI_DEVICE_STATES = { 95 | "0" : "disconnected", 96 | "1" : "connected", 97 | "2" : "pending" 98 | } 99 | 100 | 101 | ############ Controller ############ 102 | def discovery_unifi_controller(section): 103 | yield Service(item="Unifi Controller") 104 | if section.cloudkey_version: 105 | yield Service(item="Cloudkey") 106 | 107 | def check_unifi_controller(item,section): 108 | if item == "Unifi Controller": 109 | yield Result( 110 | state=State.OK, 111 | summary=f"Version: {section.controller_version}" 112 | ) 113 | if int(section.update_available) > 0: 114 | yield Result( 115 | state=State.WARN, 116 | notice=_("Update available") 117 | ) 118 | if item == "Cloudkey": 119 | yield Result( 120 | state=State.OK, 121 | summary=f"Version: {section.cloudkey_version}" 122 | ) 123 | if _safe_int(section.cloudkey_update_available) > 0: 124 | yield Result( 125 | state=State.WARN, 126 | notice=_("Update available") 127 | ) 128 | 129 | def inventory_unifi_controller(section): 130 | yield Attributes( 131 | path=["software","os"], 132 | inventory_attributes={ 133 | "controller_version" : section.get("controller_version") 134 | } 135 | ) 136 | 137 | register.agent_section( 138 | name = 'unifi_controller', 139 | parse_function = parse_unifi_dict 140 | ) 141 | 142 | register.check_plugin( 143 | name='unifi_controller', 144 | service_name='%s', 145 | discovery_function=discovery_unifi_controller, 146 | check_function=check_unifi_controller, 147 | ) 148 | 149 | register.inventory_plugin( 150 | name = "unifi_controller", 151 | inventory_function = inventory_unifi_controller 152 | ) 153 | 154 | ############ SITES ########### 155 | 156 | def discovery_unifi_sites(section): 157 | for _item in section.values(): 158 | yield Service(item=f"{_item.desc}") 159 | 160 | def check_unifi_sites(item,params,section): 161 | site = next(filter(lambda x: x.desc == item,section.values())) 162 | yield Metric("satisfaction",max(0,_safe_int(site.satisfaction))) 163 | 164 | if site.lan_status != "unknown": 165 | yield Metric("lan_user_sta",_safe_int(site.lan_num_user)) 166 | yield Metric("lan_guest_sta",_safe_int(site.lan_num_guest)) 167 | yield Metric("if_in_octets",_safe_int(site.lan_rx_bytes_r)) 168 | #yield Metric("if_in_bps",_safe_int(site.lan_rx_bytes_r)*8) 169 | yield Metric("if_out_octets",_safe_int(site.lan_tx_bytes_r)) 170 | #yield Metric("if_out_bps",_safe_int(site.lan_tx_bytes_r)*8) 171 | yield Metric("lan_active_sw",_safe_int(site.lan_num_sw)) 172 | yield Metric("lan_total_sw",_safe_int(site.lan_num_adopted)) 173 | yield Result( 174 | state=_unifi_status2state(site.lan_status), 175 | summary=f"LAN: {site.lan_num_sw}/{site.lan_num_adopted} Switch ({site.lan_status})" 176 | ) 177 | #yield Result( 178 | # state=_expect_number(site.lan_num_disconnected), 179 | # notice=f"{site.lan_num_disconnected} Switch disconnected" ##disconnected kann 180 | #) 181 | 182 | if site.wlan_status != "unknown": 183 | yield Metric("wlan_user_sta",_safe_int(site.wlan_num_user)) 184 | yield Metric("wlan_guest_sta",_safe_int(site.wlan_num_guest)) 185 | yield Metric("wlan_iot_sta",_safe_int(site.wlan_num_iot)) 186 | yield Metric("wlan_if_in_octets",_safe_int(site.wlan_rx_bytes_r)) 187 | yield Metric("wlan_if_out_octets",_safe_int(site.wlan_tx_bytes_r)) 188 | yield Metric("wlan_active_ap",_safe_int(site.wlan_num_ap)) 189 | yield Metric("wlan_total_ap",_safe_int(site.wlan_num_adopted)) 190 | yield Result( 191 | state=_unifi_status2state(site.wlan_status), 192 | summary=f"WLAN: {site.wlan_num_ap}/{site.wlan_num_adopted} AP ({site.wlan_status})" 193 | ) 194 | #yield Result( 195 | # state=_expect_number(site.wlan_num_disconnected), 196 | # notice=f"{site.wlan_num_disconnected} AP disconnected" 197 | #) 198 | if site.wan_status != "unknown": 199 | yield Result( 200 | state=_unifi_status2state(site.wan_status), 201 | summary=f"WAN Status: {site.wan_status}" 202 | ) 203 | if site.www_status != "unknown": 204 | yield Result( 205 | state=_unifi_status2state(site.www_status), 206 | summary=f"WWW Status: {site.www_status}" 207 | ) 208 | if site.vpn_status != "unknown": 209 | yield Result( 210 | state=_unifi_status2state(site.vpn_status), 211 | notice=f"WWW Status: {site.vpn_status}" 212 | ) 213 | 214 | if params.get("ignore_alarms"): 215 | _alarmstate = State.OK 216 | else: 217 | _alarmstate = _expect_number(site.num_new_alarms) 218 | 219 | yield Result( 220 | state=_alarmstate, 221 | notice=f"{site.num_new_alarms} new Alarm" 222 | ) 223 | 224 | 225 | register.agent_section( 226 | name = 'unifi_sites', 227 | parse_function = parse_unifi_nested_dict 228 | ) 229 | 230 | register.check_plugin( 231 | name='unifi_sites', 232 | service_name='Site %s', 233 | discovery_function=discovery_unifi_sites, 234 | check_default_parameters={}, 235 | check_ruleset_name="unifi_sites", 236 | check_function=check_unifi_sites, 237 | ) 238 | 239 | ############ DEVICE_SHORTLIST ########## 240 | 241 | def inventory_unifi_device_shortlist(section): 242 | for _name,_device in section.items(): 243 | yield TableRow( 244 | path=["hardware","networkdevices"], 245 | key_columns={"_name" : _name}, 246 | inventory_columns={ 247 | "serial" : _device.get("serial"), 248 | "_state" : UNIFI_DEVICE_STATES.get(_device.state,"unknown"), 249 | "vendor" : "ubiquiti", 250 | "model" : _device.get("model_name",_device.get("model")), 251 | "version" : _device.version, 252 | "ip_address": _device.ip, 253 | "mac" : _device.mac 254 | } 255 | ) 256 | 257 | register.agent_section( 258 | name = 'unifi_device_shortlist', 259 | parse_function = parse_unifi_nested_dict 260 | ) 261 | 262 | register.inventory_plugin( 263 | name = "unifi_device_shortlist", 264 | inventory_function = inventory_unifi_device_shortlist 265 | ) 266 | 267 | 268 | 269 | ############ DEVICE ########### 270 | def discovery_unifi_device(section): 271 | yield Service(item="Device Status") 272 | yield Service(item="Unifi Device") 273 | yield Service(item="Active-User") 274 | if section.type != "uap": # kein satisfaction bei ap .. radio/ssid haben schon 275 | yield Service(item="Satisfaction") 276 | if section.general_temperature: 277 | yield Service(item="Temperature") 278 | if section.uplink_device: 279 | yield Service(item="Uplink") 280 | if section.speedtest_status: 281 | yield Service(item="Speedtest") 282 | 283 | def check_unifi_device(item,section): 284 | _device_state = UNIFI_DEVICE_STATES.get(section.state,"unknown") 285 | ## connected OK / pending Warn / Rest Crit 286 | _hoststatus = State.OK if section.state == "1" else State.WARN if section.state == "2" else State.CRIT 287 | if item == "Device Status": 288 | yield Result( 289 | state=_hoststatus, 290 | summary=f"Status: {_device_state}" 291 | ) 292 | #if section.state != "1": 293 | # yield IgnoreResults(f"device not active State: {section.state}") 294 | 295 | if item == "Unifi Device": 296 | yield Result( 297 | state=State.OK, 298 | summary=f"Version: {section.version}" 299 | ) 300 | if _safe_int(section.upgradable) > 0: 301 | yield Result( 302 | state=State.WARN, 303 | notice=_("Update available") 304 | ) 305 | if item == "Active-User": 306 | _active_user = _safe_int(section.user_num_sta) 307 | yield Result( 308 | state=State.OK, 309 | summary=f"{_active_user}" 310 | ) 311 | if _safe_int(section.guest_num_sta) > -1: 312 | yield Result( 313 | state=State.OK, 314 | summary=f"Guest: {section.guest_num_sta}" 315 | ) 316 | yield Metric("user_sta",_active_user) 317 | yield Metric("guest_sta",_safe_int(section.guest_num_sta)) 318 | if item == "Satisfaction": 319 | yield Result( 320 | state=State.OK, 321 | summary=f"{section.satisfaction}%" 322 | ) 323 | yield Metric("satisfaction",max(0,_safe_int(section.satisfaction))) 324 | if item == "Temperature": 325 | yield Metric("temp",_safe_float(section.general_temperature)) 326 | yield Result( 327 | state=State.OK, 328 | summary=f"{section.general_temperature} °C" 329 | ) 330 | if section.fan_level: 331 | yield Result( 332 | state=State.OK, 333 | summary=f"Fan: {section.fan_level}%" 334 | ) 335 | if item == "Speedtest": 336 | yield Result( 337 | state=State.OK, 338 | summary=f"Ping: {section.speedtest_ping} ms" 339 | ) 340 | yield Result( 341 | state=State.OK, 342 | summary=f"Down: {section.speedtest_download} Mbit/s" 343 | ) 344 | yield Result( 345 | state=State.OK, 346 | summary=f"Up: {section.speedtest_upload} Mbit/s" 347 | ) 348 | _speedtest_time = render.datetime(_safe_float(section.speedtest_time)) 349 | yield Result( 350 | state=State.OK, 351 | summary=f"Last: {_speedtest_time}" 352 | ) 353 | yield Metric("rtt",_safe_float(section.speedtest_ping)) 354 | yield Metric("if_in_bps",_safe_float(section.speedtest_download)*1024*1024) ## mbit to bit 355 | yield Metric("if_out_bps",_safe_float(section.speedtest_upload)*1024*1024) ## mbit to bit 356 | 357 | if item == "Uplink": 358 | yield Result( 359 | state=_expect_bool(section.uplink_up), 360 | summary=f"Device {section.uplink_device} Port: {section.uplink_remote_port}" 361 | ) 362 | 363 | def inventory_unifi_device(section): 364 | yield Attributes( 365 | path=["software","os"], 366 | inventory_attributes={ 367 | "version" : section.get("version") 368 | } 369 | ) 370 | yield Attributes( 371 | path=["software","configuration","snmp_info"], 372 | inventory_attributes = { 373 | "name" : section.get("name"), 374 | "contact" : section.get("snmp_contact"), 375 | "location" : section.get("snmp_location") 376 | } 377 | ) 378 | _hwdict = { 379 | "vendor" : "ubiquiti", 380 | } 381 | for _key in ("model","board_rev","serial","mac"): 382 | _val = section.get(_key) 383 | if _val: 384 | _hwdict[_key] = _val 385 | yield Attributes( 386 | path=["hardware","system"], 387 | inventory_attributes= _hwdict 388 | ) 389 | 390 | register.agent_section( 391 | name = 'unifi_device', 392 | parse_function = parse_unifi_dict 393 | ) 394 | 395 | register.check_plugin( 396 | name='unifi_device', 397 | service_name='%s', 398 | discovery_function=discovery_unifi_device, 399 | check_function=check_unifi_device, 400 | ) 401 | 402 | register.inventory_plugin( 403 | name = "unifi_device", 404 | inventory_function = inventory_unifi_device 405 | ) 406 | 407 | ############ DEVICEPORT ########### 408 | @dataclass 409 | class unifi_interface(interfaces.InterfaceWithCounters): 410 | jumbo : bool = False 411 | satisfaction : int = 0 412 | poe_enable : bool = False 413 | poe_mode : Optional[str] = None 414 | poe_good : Optional[bool] = None 415 | poe_current : Optional[float] = 0 416 | poe_power : Optional[float] = 0 417 | poe_voltage : Optional[float] = 0 418 | poe_class : Optional[str] = None 419 | dot1x_mode : Optional[str] = None 420 | dot1x_status : Optional[str] = None 421 | ip_address : Optional[str] = None 422 | portconf : Optional[str] = None 423 | 424 | 425 | def _convert_unifi_counters_if(section: Section) -> interfaces.Section: 426 | ## 10|port_idx|10 427 | ## 10|port_poe|1 428 | ## 10|poe_caps|7 429 | ## 10|op_mode|switch 430 | ## 10|poe_mode|auto 431 | ## 10|anomalies|0 432 | ## 10|autoneg|1 433 | ## 10|dot1x_mode|unknown 434 | ## 10|dot1x_status|disabled 435 | ## 10|enable|1 436 | ## 10|full_duplex|1 437 | ## 10|is_uplink|0 438 | ## 10|jumbo|1 439 | ## 10|poe_class|Unknown 440 | ## 10|poe_current|0.00 441 | ## 10|poe_enable|0 442 | ## 10|poe_good|0 443 | ## 10|poe_power|0.00 444 | ## 10|poe_voltage|0.00 445 | ## 10|rx_broadcast|1290 446 | ## 10|rx_bytes|38499976384 447 | ## 10|rx_dropped|0 448 | ## 10|rx_errors|0 449 | ## 10|rx_multicast|16423 450 | ## 10|rx_packets|125489263 451 | ## 10|satisfaction|100 452 | ## 10|satisfaction_reason|0 453 | ## 10|speed|1000 454 | ## 10|stp_pathcost|20000 455 | ## 10|stp_state|forwarding 456 | ## 10|tx_broadcast|20791854 457 | ## 10|tx_bytes|238190158091 458 | ## 10|tx_dropped|0 459 | ## 10|tx_errors|0 460 | ## 10|tx_multicast|262691 461 | ## 10|tx_packets|228482694 462 | ## 10|tx_bytes_r|17729 463 | ## 10|rx_bytes_r|176941 464 | ## 10|bytes_r|194671 465 | ## 10|name|Port 10 466 | ## 10|aggregated_by|0 467 | ## 10|oper_status|1 468 | ## 10|admin_status|1 469 | ## 10|portconf|ALL 470 | ## unifi_interface(index='10', descr='Port 10', alias='Port 10', type='6', speed=1000000000, oper_status='1', 471 | ## in_octets=38448560321, in_ucast=125404491, in_mcast=16414, in_bcast=1290, in_discards=0, in_errors=0, 472 | ## out_octets=238185160794, out_ucast=228451699, out_mcast=262551, out_bcast=20783341, out_discards=0, out_errors=0, 473 | ## out_qlen=0, phys_address='', oper_status_name='up', speed_as_text='', group=None, node=None, admin_status='1', 474 | ## total_octets=276633721115, jumbo=True, satisfaction=100, 475 | ## poe_enable=False, poe_mode='auto', poe_good=None, poe_current=0.0, poe_power=0.0, poe_voltage=0.0, poe_class='Unknown', 476 | ## dot1x_mode='unknown',dot1x_status='disabled', ip_address='', portconf='ALL') 477 | 478 | return [ 479 | unifi_interface( 480 | attributes=interfaces.Attributes( 481 | index=str(netif.port_idx), 482 | descr=netif.name, 483 | alias=netif.name, 484 | type='6', 485 | speed=_safe_int(netif.speed)*1000000, 486 | oper_status=netif.oper_status, 487 | admin_status=netif.admin_status, 488 | ), 489 | counters=interfaces.Counters( 490 | in_octets=_safe_int(netif.rx_bytes), 491 | in_ucast=_safe_int(netif.rx_packets), 492 | in_mcast=_safe_int(netif.rx_multicast), 493 | in_bcast=_safe_int(netif.rx_broadcast), 494 | in_disc=_safe_int(netif.rx_dropped), 495 | in_err=_safe_int(netif.rx_errors), 496 | out_octets=_safe_int(netif.tx_bytes), 497 | out_ucast=_safe_int(netif.tx_packets), 498 | out_mcast=_safe_int(netif.tx_multicast), 499 | out_bcast=_safe_int(netif.tx_broadcast), 500 | out_disc=_safe_int(netif.tx_dropped), 501 | out_err=_safe_int(netif.tx_errors), 502 | ), 503 | jumbo=True if netif.jumbo == "1" else False, 504 | satisfaction=_safe_int(netif.satisfaction) if netif.satisfaction and netif.oper_status == "1" else 0, 505 | poe_enable=True if netif.poe_enable == "1" else False, 506 | poe_mode=netif.poe_mode, 507 | poe_current=float(netif.poe_current) if netif.poe_current else 0, 508 | poe_voltage=float(netif.poe_voltage) if netif.poe_voltage else 0, 509 | poe_power=float(netif.poe_power) if netif.poe_power else 0, 510 | poe_class=netif.poe_class, 511 | dot1x_mode=netif.dot1x_mode, 512 | dot1x_status=netif.dot1x_status, 513 | ip_address=netif.ip, 514 | portconf=netif.portconf 515 | ) for netif in parse_unifi_nested_dict(section).values() 516 | ] 517 | 518 | 519 | 520 | def discovery_unifi_network_port_if( ## fixme parsed_section_name 521 | params: Sequence[Mapping[str, Any]], 522 | section: Section, 523 | ) -> DiscoveryResult: 524 | yield from interfaces.discover_interfaces( 525 | params, 526 | _convert_unifi_counters_if(section), 527 | ) 528 | 529 | 530 | def check_unifi_network_port_if( ##fixme parsed_section_name 531 | item: str, 532 | params: Mapping[str, Any], 533 | section: Section, 534 | ) -> CheckResult: 535 | _converted_ifs = _convert_unifi_counters_if(section) 536 | iface = next(filter(lambda x: _safe_int(item,-1) == _safe_int(x.attributes.index) or item == x.attributes.alias,_converted_ifs),None) ## fix Service Discovery appearance alias/descr 537 | yield from interfaces.check_multiple_interfaces( 538 | item, 539 | params, 540 | _converted_ifs, 541 | ) 542 | if iface: 543 | #pprint(iface) 544 | if iface.portconf: 545 | yield Result( 546 | state=State.OK, 547 | summary=f"Network: {iface.portconf}" 548 | ) 549 | yield Metric("satisfaction",max(0,iface.satisfaction)) 550 | #pprint(iface) 551 | if iface.poe_enable: 552 | yield Result( 553 | state=State.OK, 554 | summary=f"PoE: {iface.poe_power}W" 555 | ) 556 | #yield Metric("poe_current",iface.poe_current) 557 | yield Metric("poe_power",iface.poe_power) 558 | yield Metric("poe_voltage",iface.poe_voltage) 559 | if iface.ip_address: 560 | yield Result( 561 | state=State.OK, 562 | summary=f"IP: {iface.ip_address}" 563 | ) 564 | 565 | def inventory_unifi_network_ports(section): 566 | _total_ethernet_ports = 0 567 | _available_ethernet_ports = 0 568 | for _iface in parse_unifi_nested_dict(section).values(): 569 | _total_ethernet_ports +=1 570 | _available_ethernet_ports +=1 if _iface.oper_status == '2' else 0 571 | yield TableRow( 572 | path=["networking","interfaces"], 573 | key_columns={"index" : _safe_int(_iface.port_idx)}, 574 | inventory_columns={ 575 | "description" : _iface.name, 576 | "alias" : _iface.name, 577 | "speed" : _safe_int(_iface.speed)*1000000, 578 | "oper_status" : _safe_int(_iface.oper_status), 579 | "admin_status" : _safe_int(_iface.admin_status), 580 | "available" : _iface.oper_status == '2', 581 | "vlans" : _iface.portconf, 582 | "port_type" : 6, 583 | } 584 | ) 585 | 586 | yield Attributes( 587 | path=["networking"], 588 | inventory_attributes={ 589 | "available_ethernet_ports" : _available_ethernet_ports, 590 | "total_ethernet_ports" : _total_ethernet_ports, 591 | "total_interfaces" : _total_ethernet_ports 592 | } 593 | ) 594 | 595 | register.check_plugin( 596 | name='unifi_network_ports_if', 597 | sections=["unifi_network_ports"], 598 | service_name='Interface %s', 599 | discovery_ruleset_name="inventory_if_rules", 600 | discovery_ruleset_type=register.RuleSetType.ALL, 601 | discovery_default_parameters=dict(interfaces.DISCOVERY_DEFAULT_PARAMETERS), 602 | discovery_function=discovery_unifi_network_port_if, 603 | check_ruleset_name="if", 604 | check_default_parameters=interfaces.CHECK_DEFAULT_PARAMETERS, 605 | check_function=check_unifi_network_port_if, 606 | ) 607 | 608 | register.inventory_plugin( 609 | name = "unifi_network_ports", 610 | inventory_function = inventory_unifi_network_ports 611 | ) 612 | 613 | ############ DEVICERADIO ########### 614 | def discovery_unifi_radios(section): 615 | #pprint(section) 616 | for _radio in section.values(): 617 | if _radio.radio == "ng": 618 | yield Service(item="2.4Ghz") 619 | if _radio.radio == "na": 620 | yield Service(item="5Ghz") 621 | 622 | def check_unifi_radios(item,section): 623 | _item = { "2.4Ghz" : "ng", "5Ghz" : "na" }.get(item) 624 | radio = next(filter(lambda x: x.radio == _item,section.values())) 625 | yield Metric("read_data",_safe_int(radio.rx_bytes)) 626 | yield Metric("write_data",_safe_int(radio.tx_bytes)) 627 | yield Metric("satisfaction",max(0,_safe_int(radio.satisfaction))) 628 | yield Metric("wlan_user_sta",_safe_int(radio.user_num_sta)) 629 | yield Metric("wlan_guest_sta",_safe_int(radio.guest_num_sta)) 630 | yield Metric("wlan_iot_sta",_safe_int(radio.iot_num_sta)) 631 | 632 | yield Result( 633 | state=State.OK, 634 | summary=f"Channel: {radio.channel}" 635 | ) 636 | yield Result( 637 | state=State.OK, 638 | summary=f"Satisfaction: {radio.satisfaction}" 639 | ) 640 | yield Result( 641 | state=State.OK, 642 | summary=f"User: {radio.num_sta}" 643 | ) 644 | yield Result( 645 | state=State.OK, 646 | summary=f"Guest: {radio.guest_num_sta}" 647 | ) 648 | 649 | register.agent_section( 650 | name = 'unifi_network_radios', 651 | parse_function = parse_unifi_nested_dict 652 | ) 653 | 654 | register.check_plugin( 655 | name='unifi_network_radios', 656 | service_name='Radio %s', 657 | discovery_function=discovery_unifi_radios, 658 | check_function=check_unifi_radios, 659 | ) 660 | 661 | 662 | ############ SSIDs ########### 663 | def discovery_unifi_ssids(section): 664 | for _ssid in section: 665 | yield Service(item=_ssid) 666 | 667 | def check_unifi_ssids(item,section): 668 | ssid = section.get(item) 669 | if ssid: 670 | _channels = ",".join(list(filter(lambda x: _safe_int(x) > 0,[ssid.ng_channel,ssid.na_channel]))) 671 | yield Result( 672 | state=State.OK, 673 | summary=f"Channels: {_channels}" 674 | ) 675 | if (_safe_int(ssid.ng_is_guest) + _safe_int(ssid.na_is_guest)) > 0: 676 | yield Result( 677 | state=State.OK, 678 | summary="Guest" 679 | ) 680 | _satisfaction = max(0,min(_safe_int(ssid.ng_satisfaction),_safe_int(ssid.na_satisfaction))) 681 | yield Result( 682 | state=State.OK, 683 | summary=f"Satisfaction: {_satisfaction}" 684 | ) 685 | _num_sta = _safe_int(ssid.na_num_sta) + _safe_int(ssid.ng_num_sta) 686 | if _num_sta > 0: 687 | yield Result( 688 | state=State.OK, 689 | summary=f"User: {_num_sta}" 690 | ) 691 | yield Metric("satisfaction",max(0,_satisfaction)) 692 | yield Metric("wlan_24Ghz_num_user",_safe_int(ssid.ng_num_sta) ) 693 | yield Metric("wlan_5Ghz_num_user",_safe_int(ssid.na_num_sta) ) 694 | 695 | yield Metric("na_avg_client_signal",_safe_int(ssid.na_avg_client_signal)) 696 | yield Metric("ng_avg_client_signal",_safe_int(ssid.ng_avg_client_signal)) 697 | 698 | yield Metric("na_tcp_packet_loss",_safe_int(ssid.na_tcp_packet_loss)) 699 | yield Metric("ng_tcp_packet_loss",_safe_int(ssid.ng_tcp_packet_loss)) 700 | 701 | yield Metric("na_wifi_retries",_safe_int(ssid.na_wifi_retries)) 702 | yield Metric("ng_wifi_retries",_safe_int(ssid.ng_wifi_retries)) 703 | yield Metric("na_wifi_latency",_safe_int(ssid.na_wifi_latency)) 704 | yield Metric("ng_wifi_latency",_safe_int(ssid.ng_wifi_latency)) 705 | 706 | 707 | 708 | register.agent_section( 709 | name = 'unifi_network_ssids', 710 | parse_function = parse_unifi_nested_dict 711 | ) 712 | 713 | register.check_plugin( 714 | name='unifi_network_ssids', 715 | service_name='SSID: %s', 716 | discovery_function=discovery_unifi_ssids, 717 | check_function=check_unifi_ssids, 718 | ) 719 | 720 | 721 | ############ SSIDsListController ########### 722 | def discovery_unifi_ssidlist(section): 723 | #pprint(section) 724 | for _ssid in section: 725 | yield Service(item=_ssid) 726 | 727 | def check_unifi_ssidlist(item,section): 728 | ssid = section.get(item) 729 | if ssid: 730 | yield Result( 731 | state=State.OK, 732 | summary=f"Channels: {ssid.channels}" 733 | ) 734 | yield Result( 735 | state=State.OK, 736 | summary=f"User: {ssid.num_sta}" 737 | ) 738 | yield Metric("wlan_24Ghz_num_user",_safe_int(ssid.ng_num_sta) ) 739 | yield Metric("wlan_5Ghz_num_user",_safe_int(ssid.na_num_sta) ) 740 | yield Metric("na_avg_client_signal",_safe_int(ssid.na_avg_client_signal)) 741 | yield Metric("ng_avg_client_signal",_safe_int(ssid.ng_avg_client_signal)) 742 | 743 | yield Metric("na_tcp_packet_loss",_safe_int(ssid.na_tcp_packet_loss)) 744 | yield Metric("ng_tcp_packet_loss",_safe_int(ssid.ng_tcp_packet_loss)) 745 | 746 | yield Metric("na_wifi_retries",_safe_int(ssid.na_wifi_retries)) 747 | yield Metric("ng_wifi_retries",_safe_int(ssid.ng_wifi_retries)) 748 | yield Metric("na_wifi_latency",_safe_int(ssid.na_wifi_latency)) 749 | yield Metric("ng_wifi_latency",_safe_int(ssid.ng_wifi_latency)) 750 | 751 | register.agent_section( 752 | name = 'unifi_ssid_list', 753 | parse_function = parse_unifi_nested_dict 754 | ) 755 | 756 | register.check_plugin( 757 | name='unifi_ssid_list', 758 | service_name='SSID: %s', 759 | discovery_function=discovery_unifi_ssidlist, 760 | check_function=check_unifi_ssidlist, 761 | ) 762 | 763 | 764 | -------------------------------------------------------------------------------- /share/check_mk/agents/special/agent_unifi_controller: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf-8; py-indent-offset: 4 -*- 3 | ## MIT License 4 | ## 5 | ## Copyright (c) 2024 Bash Club 6 | ## 7 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 8 | ## of this software and associated documentation files (the "Software"), to deal 9 | ## in the Software without restriction, including without limitation the rights 10 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ## copies of the Software, and to permit persons to whom the Software is 12 | ## furnished to do so, subject to the following conditions: 13 | ## 14 | ## The above copyright notice and this permission notice shall be included in all 15 | ## copies or substantial portions of the Software. 16 | ## 17 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | ## SOFTWARE. 24 | 25 | ### 26 | __VERSION__ = 2.01 27 | 28 | import sys 29 | import socket 30 | import re 31 | import json 32 | import requests 33 | from urllib3.exceptions import InsecureRequestWarning 34 | from statistics import mean 35 | from collections import defaultdict 36 | 37 | from pprint import pprint 38 | try: 39 | import cmk.utils.paths 40 | AGENT_TMP_PATH = cmk.utils.paths.Path(cmk.utils.paths.tmp_dir, "agents/agent_unifi") 41 | except ImportError: 42 | AGENT_TMP_PATH = None 43 | 44 | UNIFI_DEVICE_TABLE = { 45 | 'BZ2' : 'UAP', 46 | 'BZ2LR' : 'UAP-LR', 47 | 'S216150' : 'US-16-150W', 48 | 'S224250' : 'US-24-250W', 49 | 'S224500' : 'US-24-500W', 50 | 'S248500' : 'US-48-500W', 51 | 'S248750' : 'US-48-750W', 52 | 'S28150' : 'US-8-150W', 53 | 'U2HSR' : 'UAP-Outdoor+', 54 | 'U2IW' : 'UAP-IW', 55 | 'U2L48' : 'UAP-LR', 56 | 'U2Lv2' : 'UAP-LRv2', 57 | 'U2M' : 'UAP-Mini', 58 | 'U2O' : 'UAP-Outdoor', 59 | 'U2S48' : 'UAP', 60 | 'U2Sv2' : 'UAPv2', 61 | 'U5O' : 'UAP-Outdoor5', 62 | 'U6ENT' : 'U6-Enterprise', 63 | 'U6EXT' : 'U6-Extender', 64 | 'U6IW' : 'U6-IW', 65 | 'U6M' : 'U6-Mesh', 66 | 'U7E' : 'UAP-AC', 67 | 'U7EDU' : 'UAP-AC-EDU', 68 | 'U7Ev2' : 'UAP-AC', 69 | 'U7HD' : 'UAP-AC-HD', 70 | 'U7IW' : 'UAP-AC-IW', 71 | 'U7IWP' : 'UAP-AC-IW-Pro', 72 | 'U7LR' : 'UAP-AC-LR', 73 | 'U7LT' : 'UAP-AC-Lite', 74 | 'U7MP' : 'UAP-AC-M-Pro', 75 | 'U7MSH' : 'UAP-AC-M', 76 | 'U7NHD' : 'UAP-nanoHD', 77 | 'U7O' : 'UAP-AC-Outdoor', 78 | 'U7P' : 'UAP-AC-Pro', 79 | 'U7PG2' : 'UAP-AC-Pro', 80 | 'U7SHD' : 'UAP-AC-SHD', 81 | 'UAE6' : 'U6-Extender-EA', 82 | 'UAIW6' : 'U6-IW-EA', 83 | 'UAL6' : 'U6-Lite', 84 | 'UALR6' : 'U6-LR-EA', 85 | 'UALR6v2' : 'U6-LR', 86 | 'UALR6v3' : 'U6-LR', 87 | 'UAM6' : 'U6-Mesh-EA', 88 | 'UAP6' : 'U6-LR', 89 | 'UAP6MP' : 'U6-Pro', 90 | 'UASXG' : 'UAS-XG', 91 | 'UBB' : 'UBB', 92 | 'UBBXG' : 'UBB-XG', 93 | 'UCK' : 'UCK', 94 | 'UCK-v2' : 'UCK', 95 | 'UCK-v3' : 'UCK', 96 | 'UCKG2' : 'UCK-G2', 97 | 'UCKP' : 'UCK-G2-Plus', 98 | 'UCMSH' : 'UAP-XG-Mesh', 99 | 'UCXG' : 'UAP-XG', 100 | 'UDC48X6' : 'USW-Leaf', 101 | 'UDM' : 'UDM', 102 | 'UDMB' : 'UAP-BeaconHD', 103 | 'UDMPRO' : 'UDM-Pro', 104 | 'UDMPROSE' : 'UDM-SE', 105 | 'UDR' : 'UDR', 106 | 'UDW' : 'UDW', 107 | 'UDWPRO' : 'UDWPRO', 108 | 'UFLHD' : 'UAP-FlexHD', 109 | 'UGW3' : 'USG-3P', 110 | 'UGW4' : 'USG-Pro-4', 111 | 'UGWHD4' : 'USG', 112 | 'UGWXG' : 'USG-XG-8', 113 | 'UHDIW' : 'UAP-IW-HD', 114 | 'ULTE' : 'U-LTE', 115 | 'ULTEPEU' : 'U-LTE-Pro', 116 | 'ULTEPUS' : 'U-LTE-Pro', 117 | 'UP1' : 'USP-Plug', 118 | 'UP4' : 'UVP-X', 119 | 'UP5' : 'UVP', 120 | 'UP5c' : 'UVP', 121 | 'UP5t' : 'UVP-Pro', 122 | 'UP5tc' : 'UVP-Pro', 123 | 'UP6' : 'USP-Strip', 124 | 'UP7' : 'UVP-Executive', 125 | 'UP7c' : 'UVP-Executive', 126 | 'US16P150' : 'US-16-150W', 127 | 'US24' : 'USW-24-G1', 128 | 'US24P250' : 'US-24-250W', 129 | 'US24P500' : 'US-24-500W', 130 | 'US24PL2' : 'US-L2-24-PoE', 131 | 'US24PRO' : 'USW-Pro-24-PoE', 132 | 'US24PRO2' : 'USW-Pro-24', 133 | 'US48' : 'US-48-G1', 134 | 'US48P500' : 'US-48-500W', 135 | 'US48P750' : 'US-48-750W', 136 | 'US48PL2' : 'US-L2-48-PoE', 137 | 'US48PRO' : 'USW-Pro-48-PoE', 138 | 'US48PRO2' : 'USW-Pro-48', 139 | 'US624P' : 'USW-Enterprise-24-PoE', 140 | 'US648P' : 'USW-Enterprise-48-PoE', 141 | 'US68P' : 'USW-Enterprise-8-PoE', 142 | 'US6XG150' : 'US-XG-6PoE', 143 | 'US8' : 'US-8', 144 | 'US8P150' : 'US-8-150W', 145 | 'US8P60' : 'US-8-60W', 146 | 'USAGGPRO' : 'USW-Pro-Aggregation', 147 | 'USC8' : 'US-8', 148 | 'USC8P150' : 'US-8-150W', 149 | 'USC8P450' : 'USW-Industrial', 150 | 'USC8P60' : 'US-8-60W', 151 | 'USF5P' : 'USW-Flex', 152 | 'USFXG' : 'USW-Flex-XG', 153 | 'USL16LP' : 'USW-Lite-16-PoE', 154 | 'USL16P' : 'USW-16-PoE', 155 | 'USL24' : 'USW-24-G2', 156 | 'USL24P' : 'USW-24-PoE', 157 | 'USL48' : 'USW-48-G2', 158 | 'USL48P' : 'USW-48-PoE', 159 | 'USL8A' : 'USW-Aggregation', 160 | 'USL8LP' : 'USW-Lite-8-PoE', 161 | 'USL8MP' : 'USW-Mission-Critical', 162 | 'USMINI' : 'USW-Flex-Mini', 163 | 'USPPDUP' : 'USP-PDU-Pro', 164 | 'USPRPS' : 'USP-RPS', 165 | 'USXG' : 'US-16-XG', 166 | 'USXG24' : 'USW-EnterpriseXG-24', 167 | 'UXBSDM' : 'UWB-XG-BK', 168 | 'UXGPRO' : 'UXG-Pro', 169 | 'UXSDM' : 'UWB-XG', 170 | 'p2N' : 'PICOM2HP' 171 | } 172 | 173 | try: 174 | from cmk.special_agents.utils.argument_parsing import create_default_argument_parser 175 | #from check_api import LOGGER ##/TODO 176 | except ImportError: 177 | from argparse import ArgumentParser as create_default_argument_parser 178 | 179 | class unifi_api_exception(Exception): 180 | pass 181 | 182 | class unifi_object(object): 183 | def __init__(self,**kwargs): 184 | for _k,_v in kwargs.items(): 185 | _k = _k.replace("-","_") 186 | if type(_v) == bool: 187 | _v = int(_v) 188 | setattr(self,_k,_v) 189 | 190 | self._PARENT = kwargs.get("_PARENT",object) 191 | if hasattr(self._PARENT,"_UNIFICONTROLLER"): 192 | self._UNIFICONTROLLER = self._PARENT._UNIFICONTROLLER 193 | self._API = self._PARENT._API 194 | if hasattr(self,"_init"): 195 | self._init() 196 | 197 | def __repr__(self): 198 | return repr([(_k,_v) for _k,_v in self.__dict__.items() if type(_v) in (int,str)]) 199 | 200 | ######################################## 201 | ###### 202 | ###### S S I D 203 | ###### 204 | ######################################## 205 | class unifi_network_ssid(unifi_object): 206 | def _init(self): 207 | self._UNIFICONTROLLER._UNIFI_SSIDS.append(self) 208 | self._UNIFI_SITE = self._PARENT._PARENT 209 | for _k,_v in getattr(self,"reasons_bar_chart_now",{}).items(): 210 | setattr(self,_k,_v) 211 | setattr(self,f"{self.radio}_num_sta",self.num_sta) 212 | setattr(self,f"{self.radio}_tcp_packet_loss",self.tcp_packet_loss) 213 | setattr(self,f"{self.radio}_wifi_retries",self.wifi_retries) 214 | setattr(self,f"{self.radio}_wifi_latency",self.wifi_latency) 215 | setattr(self,f"{self.radio}_avg_client_signal",self.avg_client_signal) 216 | def __str__(self): 217 | _ret = [] 218 | _unwanted = ["essid","radio","id","t","name","radio_name","wlanconf_id","is_wep","up","site_id","ap_mac","state", 219 | "na_num_sta","ng_num_sta","ng_tcp_packet_loss","na_tcp_packet_loss","na_wifi_retries","ng_wifi_retries", 220 | "na_wifi_latency","ng_wifi_latency","na_avg_client_signal","ng_avg_client_signal" 221 | ] 222 | for _k,_v in self.__dict__.items(): 223 | if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float): 224 | continue 225 | _ret.append(f"{self.essid}|{self.radio}_{_k}|{_v}") 226 | return "\n".join(_ret) 227 | 228 | ######################################## 229 | ###### 230 | ###### R A D I O 231 | ###### 232 | ######################################## 233 | class unifi_network_radio(unifi_object): 234 | def _update_stats(self,stats): 235 | _prefixlen = len(self.name) +1 236 | for _k,_v in stats.items(): 237 | if _k.startswith(self.name): 238 | if type(_v) == float: 239 | _v = int(_v) 240 | setattr(self,_k[_prefixlen:],_v) 241 | def __str__(self): 242 | _ret = [] 243 | _unwanted = ["name","ast_be_xmit","extchannel","cu_total","cu_self_rx","cu_self_tx"] 244 | for _k,_v in self.__dict__.items(): 245 | if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float): 246 | continue 247 | _ret.append(f"{self.name}|{_k}|{_v}") 248 | return "\n".join(_ret) 249 | 250 | ######################################## 251 | ###### 252 | ###### P O R T 253 | ###### 254 | ######################################## 255 | class unifi_network_port(unifi_object): 256 | def _init(self): 257 | self.oper_status = self._get_state(getattr(self,"up",None)) 258 | self.admin_status = self._get_state(getattr(self,"enable",None)) 259 | if hasattr(self,"ifname"): ## GW / UDM Names 260 | _name = list(filter(lambda x: x.get("ifname") == self.ifname,self._PARENT.ethernet_overrides)) 261 | if _name: 262 | _name = _name[0] 263 | if getattr(self,"name",None) and _name.get("networkgroup") != "LAN": 264 | self.name = _name.get("networkgroup","unkn") 265 | else: 266 | self.name = self.ifname 267 | if not hasattr(self,"port_idx") and hasattr(self,"ifname"): 268 | self.port_idx = int(self.ifname[-1])+1 ## ethX 269 | 270 | self.portconf = self._PARENT._PARENT._PORTCONFIGS.get(getattr(self,"portconf_id",None)) 271 | 272 | 273 | def _get_state(self,state): 274 | return { 275 | "1" : 1, ## up 276 | "0" : 2 ## down 277 | }.get(str(state),4) ##unknown 278 | def __str__(self): 279 | _ret = [] 280 | _unwanted = ["up","enabled","media","anonymous_id","www_gw_mac","wan_gw_mac","attr_hidden_id","masked","flowctrl_tx","flowctrl_rx","portconf_id","speed_caps"] 281 | for _k,_v in self.__dict__.items(): 282 | if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float): 283 | continue 284 | _ret.append(f"{self.port_idx}|{_k}|{_v}") 285 | return "\n".join(_ret) 286 | 287 | ######################################## 288 | ###### 289 | ###### D E V I C E 290 | ###### 291 | ######################################## 292 | class unifi_device(unifi_object): 293 | def _init(self): 294 | if not hasattr(self,"name"): 295 | _mac_end = self.mac.replace(":","")[-4:] 296 | self.name = f"{self.model}:{_mac_end}" 297 | self._piggy_back = True 298 | self._PARENT._SITE_DEVICES.append(self) 299 | self._NETWORK_PORTS = [] 300 | self._NETWORK_RADIO = [] 301 | self._NETWORK_SSIDS = [] 302 | 303 | for _k,_v in getattr(self,"sys_stats",{}).items(): 304 | _k = _k.replace("-","_") 305 | setattr(self,_k,_v) 306 | self.model_name = UNIFI_DEVICE_TABLE.get(self.model) 307 | if self.type in ("ugw","udm") and hasattr(self,"connect_request_ip"): 308 | ## change ip to local ip 309 | self.wan_ip = self.ip 310 | self.ip = self.connect_request_ip 311 | 312 | if getattr(self,"speedtest_status_saved",False): 313 | _speedtest = getattr(self,"speedtest_status",{}) 314 | self.speedtest_time = int(_speedtest.get("rundate","0")) 315 | self.speedtest_status = int(_speedtest.get("status_summary","0")) 316 | self.speedtest_ping = round(_speedtest.get("latency",-1),1) 317 | self.speedtest_download = round(_speedtest.get("xput_download",0.0),1) 318 | self.speedtest_upload = round(_speedtest.get("xput_upload",0.0),1) 319 | 320 | _temp = list(map(lambda x: x.get("value",0),getattr(self,"temperatures",[]))) 321 | if _temp: 322 | self.general_temperature = "{0:.1f}".format(mean(_temp)) 323 | 324 | for _port in getattr(self,"port_table",[]): 325 | self._NETWORK_PORTS.append(unifi_network_port(_PARENT=self,**_port)) 326 | 327 | for _radio in getattr(self,"radio_table_stats",[]): 328 | _radio_obj = unifi_network_radio(_PARENT=self,**_radio) 329 | _radio_obj._update_stats(getattr(self,"stat",{}).get("ap",{})) 330 | self._NETWORK_RADIO.append(_radio_obj) 331 | 332 | for _ssid in getattr(self,"vap_table",[]): 333 | self._NETWORK_SSIDS.append(unifi_network_ssid(_PARENT=self,**_ssid)) 334 | 335 | def _get_uplink(self): 336 | if type(getattr(self,"uplink",None)) == dict: 337 | self.uplink_up = int(self.uplink.get("up","0")) 338 | self.uplink_device = self._UNIFICONTROLLER._get_device_by_mac(self.uplink.get("uplink_mac")) 339 | self.uplink_remote_port = self.uplink.get("uplink_remote_port") 340 | self.uplink_type = self.uplink.get("type") 341 | 342 | def _get_short_info(self): 343 | _ret = [] 344 | _wanted = ["version","ip","mac","serial","model","model_name","uptime","upgradeable","num_sta","adopted","state"] 345 | for _k,_v in self.__dict__.items(): 346 | if _k.startswith("_") or _k not in _wanted or type(_v) not in (str,int,float): 347 | continue 348 | _ret.append(f"{self.name}|{_k}|{_v}") 349 | return "\n".join(_ret) 350 | 351 | def __str__(self): 352 | if self._piggy_back: 353 | _piggybackname = getattr(self,self._API.PIGGYBACK_ATTRIBUT,self.name) 354 | _ret = [f"<<<<{_piggybackname}>>>>"] 355 | else: 356 | _ret = [] 357 | _ret.append("<<>>") 358 | _unwanted = ["anon_id","device_id","site_id","known_cfgversion","cfgversion","syslog_key","has_speaker","has_eth1", 359 | "next_interval","next_heartbeat","next_heartbeat_at","guest_token","connect_request_ip","connect_request_port", 360 | "start_connected_millis","start_disconnected_millis","wlangroup_id_na","wlangroup_id_ng","uplink_down_timeout" 361 | "unsupported_reason","connected_at","provisioned_at","fw_caps","hw_caps","manufacturer_id","use_custom_config", 362 | "led_override","led_override_color","led_override_color_brightness","sys_error_caps","adoptable_when_upgraded", 363 | "mesh_uplink_1","mesh_uplink_1","considered_lost_at","outdoor_mode_override","unsupported_reason","architecture", 364 | "kernel_version","required_version","prev_non_busy_state","has_fan","has_temperature","flowctrl_enabled","hash_id", 365 | "speedtest-status-saved","usg_caps","two_phase_adopt","rollupgrade","locating","dot1x_portctrl_enabled", 366 | "lcm_idle_timeout_override","lcm_brightness_override","uplink_depth","mesh_sta_vap_enabled","mesh_uplink_2", 367 | "lcm_tracker_enabled","model_incompatible","model_in_lts","model_in_eol","country_code","wifi_caps", 368 | "meshv3_peer_mac","element_peer_mac","vwireEnabled","hide_ch_width","x_authkey","x_ssh_hostkey_fingerprint", 369 | "x_fingerprint","x_inform_authkey","op_mode","uptime" 370 | ] 371 | for _k,_v in self.__dict__.items(): 372 | if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float): 373 | continue 374 | _ret.append(f"{_k}|{_v}") 375 | 376 | _ret.append("<<>>") 377 | _ret.append(f"{{\"unifi_device\":\"unifi-{self.type}\"}}") 378 | _uptime = getattr(self,"uptime",None) 379 | if _uptime: 380 | _ret.append("<<>>") 381 | _ret.append(str(_uptime)) 382 | if self._NETWORK_PORTS: 383 | _ret += ["","<<>>"] + [str(_port) for _port in self._NETWORK_PORTS] 384 | if self._NETWORK_RADIO: 385 | _ret += ["","<<>>"] + [str(_radio) for _radio in self._NETWORK_RADIO] 386 | 387 | if self._NETWORK_SSIDS: 388 | _ret += ["","<<>>"] + [str(_ssid) for _ssid in sorted(self._NETWORK_SSIDS,key=lambda x: x.essid)] 389 | return "\n".join(_ret) 390 | 391 | ######################################## 392 | ###### 393 | ###### S I T E 394 | ###### 395 | ######################################## 396 | class unifi_site(unifi_object): 397 | def _init(self): 398 | for _subsys in self.health: 399 | _name = _subsys.get("subsystem") 400 | for _k,_v in _subsys.items(): 401 | _k = _k.replace("-","_") 402 | if _k == "subsystem" or type(_v) not in (str,int,float): 403 | continue 404 | #print(f"{_k}:{_v}") 405 | setattr(self,f"{_name}_{_k}",_v) 406 | 407 | ##pprint(_api.get_data("/stat/rogueap")) 408 | self._SITE_DEVICES = [] 409 | self._PORTCONFIGS = {} 410 | self._get_portconfig() 411 | self._get_devices() 412 | _satisfaction = list(filter( 413 | lambda x: x != None,map( 414 | lambda x: getattr(x,"satisfaction",None),self._SITE_DEVICES 415 | ) 416 | )) 417 | self.satisfaction = max(0,int(mean(_satisfaction)) if _satisfaction else 0) 418 | 419 | def _get_portconfig(self): 420 | _data = self._API.get_portconfig(site=self.name) 421 | for _config in _data: 422 | self._PORTCONFIGS[_config["_id"]] = _config.get("name") 423 | 424 | def _get_devices(self): 425 | _data = self._API.get_devices(site=self.name) 426 | for _device in _data: 427 | self._UNIFICONTROLLER._UNIFI_DEVICES.append(unifi_device(_PARENT=self,**_device)) 428 | 429 | def __str__(self): 430 | _ret = ["<<>>"] 431 | _unwanted = ["name","anonymous_id","www_gw_mac","wan_gw_mac","attr_hidden_id","attr_no_delete",""] 432 | for _k,_v in self.__dict__.items(): 433 | if _k.startswith("_") or _k in _unwanted or type(_v) not in (str,int,float): 434 | continue 435 | _ret.append(f"{self.name}|{_k}|{_v}") 436 | return "\n".join(_ret) 437 | 438 | ######################################## 439 | ###### 440 | ###### C O N T R O L L E R 441 | ###### 442 | ######################################## 443 | class unifi_controller(unifi_object): 444 | def _init(self): 445 | self._UNIFICONTROLLER = self 446 | self._UNIFI_SITES = [] 447 | self._UNIFI_DEVICES = [] 448 | self._UNIFI_SSIDS = [] 449 | self._get_systemhealth() 450 | self._get_sites() 451 | for _dev in self._UNIFI_DEVICES: 452 | _dev._get_uplink() 453 | if hasattr(self,"cloudkey_version"): 454 | self.cloudkey_version = re.sub(".*?v(\d+\.\d+\.\d+\.[a-z0-9]+).*","\\1",self.cloudkey_version) 455 | self.type = getattr(self,"ubnt_device_type","unifi-sw-controller") 456 | self.controller_version = self.version 457 | delattr(self,"version") 458 | 459 | def _get_systemhealth(self): 460 | _data = self._API.get_sysinfo() 461 | _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"] 462 | if _data: 463 | for _k,_v in _data[0].items(): 464 | if _k in _wanted: 465 | if type(_v) == bool: 466 | _v = int(_v) 467 | setattr(self,_k,_v) 468 | 469 | def _get_device_by_mac(self,mac): 470 | try: 471 | return next(filter(lambda x: x.mac == mac,self._UNIFI_DEVICES)).name 472 | except StopIteration: 473 | return None 474 | 475 | def _get_sites(self): 476 | _data = self._API.get_sites() 477 | for _site in _data: 478 | if self._API.SITES and _site.get("name") not in self._API.SITES and _site.get("desc").lower() not in self._API.SITES: 479 | continue 480 | self._UNIFI_SITES.append(unifi_site(_PARENT=self,**_site)) 481 | 482 | def _get_ssidlist(self): 483 | _dict = defaultdict(list) 484 | for _ssid in self._UNIFI_SSIDS: 485 | _dict[f"{_ssid.essid}@{_ssid._UNIFI_SITE.desc}"].append(_ssid) 486 | 487 | _ret = [] 488 | for _ssid,_obj in _dict.items(): 489 | #pprint(_obj) 490 | for _key in ("num_sta","ng_num_sta","na_num_sta","ng_tcp_packet_loss","na_tcp_packet_loss","ng_wifi_retries","na_wifi_retries","ng_wifi_latency","na_wifi_latency"): 491 | _ret.append("|".join([_ssid,_key,str(sum(map(lambda x: getattr(x,_key,0),_obj)))])) 492 | 493 | _signals = list(map(lambda x: getattr(x,"ng_avg_client_signal",0),filter(lambda x: x.radio == "ng",_obj))) 494 | _ret.append("|".join([_ssid,"ng_avg_client_signal",str(mean(_signals if _signals else [0]))])) 495 | _signals = list(map(lambda x: getattr(x,"na_avg_client_signal",0),filter(lambda x: x.radio == "na",_obj))) 496 | _ret.append("|".join([_ssid,"na_avg_client_signal",str(mean(_signals if _signals else [0]))])) 497 | _ret.append("|".join([_ssid,"channels",",".join( 498 | sorted( 499 | set(map(lambda x: str(getattr(x,"channel","0")),_obj)) 500 | ,key = lambda x: int(x)) 501 | )])) 502 | _ret.append("|".join([_ssid,"avg_client_signal",str(mean(map(lambda x: getattr(x,"avg_client_signal",0),_obj))) ])) 503 | return _ret 504 | 505 | def __str__(self): 506 | _ret = ["<<>>"] 507 | for _k,_v in self.__dict__.items(): 508 | if _k.startswith("_") or type(_v) not in (str,int,float): 509 | continue 510 | _ret.append(f"{_k}|{_v}") 511 | 512 | ## check udm 513 | _has_udm = list(filter(lambda x: x.name == self.name,self._UNIFI_DEVICES)) 514 | if _has_udm: 515 | _udm = _has_udm[0] 516 | _udm._piggy_back = False 517 | _ret.append(str(_udm)) 518 | 519 | _ret.append("<<>>") 520 | _ret.append(f"{{\"unifi_device\":\"unifi-{self.type}\"}}") 521 | 522 | ## SITES ## 523 | for _site in self._UNIFI_SITES: 524 | _ret.append(str(_site)) 525 | 526 | _ret.append("<<>>") 527 | for _device in self._UNIFI_DEVICES: 528 | if _device._piggy_back: 529 | _ret.append(_device._get_short_info()) 530 | ## device list 531 | 532 | ## ssid list 533 | _ret.append("<<>>") 534 | _ret += self._get_ssidlist() 535 | 536 | if self._API.PIGGYBACK_ATTRIBUT.lower() != "none": 537 | ## PIGGYBACK DEVICES ## 538 | for _device in self._UNIFI_DEVICES: 539 | if _device._piggy_back and _device.adopted: 540 | _ret.append(str(_device)) 541 | return "\n".join(_ret) 542 | 543 | 544 | ######################################## 545 | ###### 546 | ###### A P I 547 | ###### https://ubntwiki.com/products/software/unifi-controller/api 548 | ######################################## 549 | class unifi_controller_api(object): 550 | def __init__(self,host,username,password,port,site,verify_cert,rawapi,piggybackattr,**kwargs): 551 | self.host = host 552 | self.url = f"https://{host}" 553 | if port != 443: 554 | self.url = f"https://{host}:{port}" 555 | 556 | self._verify_cert = verify_cert 557 | if not verify_cert: 558 | requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) 559 | self.RAW_API = rawapi 560 | self.PIGGYBACK_ATTRIBUT = piggybackattr 561 | self.SITES = site.lower().split(",") if site else None 562 | self._session = requests.Session() 563 | self.check_unifi_os() 564 | self.login(username,password) 565 | 566 | def check_unifi_os(self): 567 | _response = self.request("GET",url=self.url,allow_redirects=False) 568 | _osid = re.findall('UNIFI_OS_MANIFEST.*?"id":"(\w+)"',_response.text) 569 | if _osid and _osid[0] in ("UCKP","UNVR","UDMPRO","UDMENT","UDM","UDR"): 570 | self.is_unifios = _osid[0] 571 | else: 572 | self.is_unifios = [] 573 | 574 | def get_sysinfo(self): 575 | return self.get_data("/stat/sysinfo") 576 | 577 | def get_sites(self): 578 | return self.get_data("/stat/sites",site=None) 579 | 580 | def get_portconfig(self,site): 581 | return self.get_data("/rest/portconf",site=site) 582 | 583 | def get_devices(self,site): 584 | return self.get_data("/stat/device",site=site) 585 | 586 | def login(self,username,password): 587 | if self.is_unifios: 588 | url=f"{self.url}/api/auth/login" 589 | else: 590 | url=f"{self.url}/api/login" 591 | auth = { 592 | "username" : username, 593 | "password" : password, 594 | "remember" : True 595 | } 596 | _response = self.request("POST",url=url,json=auth) 597 | if _response.status_code == 404: 598 | raise unifi_api_exception("API not Found try other Port or IP") 599 | _json = _response.json() 600 | if _json.get("meta",{}).get("rc") == "ok" or _json.get("status") == "ACTIVE": 601 | return 602 | raise unifi_api_exception("Login failed") 603 | 604 | def get_data(self,path,site="default",method="GET",**kwargs): 605 | _json = self.request(method=method,path=path,site=site,**kwargs).json() 606 | if type(_json) == dict: 607 | _meta = _json.get("meta",{}) 608 | if _meta.get("rc") == "ok": 609 | return _json.get("data",[]) 610 | if _json.get("modelKey") == "nvr": 611 | return _json 612 | if type(_json) == list: 613 | return _json 614 | raise unifi_api_exception(_meta.get("msg",_json.get("errors",repr(_json)))) 615 | 616 | def request(self,method,url=None,path=None,site=None,json=None,**kwargs): 617 | if not url: 618 | if self.is_unifios == "UNVR": 619 | url = f"{self.url}/proxy/protect/api" 620 | elif self.is_unifios: 621 | url = f"{self.url}/proxy/network/api" 622 | else: 623 | url = f"{self.url}/api" 624 | if site is not None: 625 | url += f"/s/{site}" 626 | if path is not None: 627 | url += f"{path}" 628 | _request = requests.Request(method,url,json=json) 629 | _prepped_request = self._session.prepare_request(_request) 630 | else: 631 | _request = requests.Request(method,url,json=json) 632 | _prepped_request = _request.prepare() 633 | _response = self._session.send(_prepped_request,verify=self._verify_cert,timeout=10,**kwargs) 634 | if _response.status_code == 200 and hasattr(_response,"json") and self.RAW_API: 635 | try: 636 | pprint(_response.json()) 637 | except: 638 | pass 639 | return _response 640 | 641 | ######################################## 642 | ###### 643 | ###### M A I N 644 | ###### 645 | ######################################## 646 | if __name__ == '__main__': 647 | parser = create_default_argument_parser(description=__doc__) 648 | parser.add_argument('-u', '--user', dest='username', required=True, 649 | help='User to access the DSM.') 650 | parser.add_argument('-p', '--password', dest='password', required=True, 651 | help='Password to access the DSM.') 652 | parser.add_argument('--ignore-cert', dest='verify_cert', action='store_false', 653 | help='Do not verify the SSL cert') 654 | parser.add_argument('-s','--site', dest='site', required=False, 655 | help='Site') 656 | parser.add_argument('--port', dest='port',type=int,default='443') 657 | parser.add_argument('--piggyback', dest='piggybackattr',type=str,default='name') 658 | parser.add_argument('--rawapi', dest='rawapi', action='store_true') 659 | parser.add_argument("host",type=str, 660 | help="""Host name or IP address of Unifi Controller""") 661 | args = parser.parse_args() 662 | try: 663 | _api = unifi_controller_api(**args.__dict__) 664 | except socket.error as e: 665 | pprint(e) 666 | sys.exit(1) 667 | 668 | if _api.is_unifios: 669 | labels = {"cmk/os_family": "UnifiOS"} 670 | print("<<>>") 671 | print(json.dumps(labels)) 672 | if _api.is_unifios == "UNVR": 673 | pprint(_api.get_data("/sensors",site=None)) 674 | pprint(_api.get_data("/cameras",site=None)) 675 | pprint(_api.get_data("/nvr",site=None)) 676 | sys.exit(0) 677 | ##pprint(_api.get_data("/stat/rogueap?within=4")) 678 | ##pprint(_api.get_data("/rest/user",site="default",method="GET")) 679 | ##pprint(_api.get_data("/stat/sta",site="default",method="GET")) 680 | ##sys.exit(0) 681 | _controller = unifi_controller(_API=_api) 682 | if args.rawapi == False: 683 | print(_controller) 684 | -------------------------------------------------------------------------------- /share/check_mk/checkman/unifi_controller: -------------------------------------------------------------------------------- 1 | title: Unifi Controller 2 | agents: unifi_controller 3 | catalog: networking 4 | licence: MIT 5 | description: 6 | plz fill me 7 | 8 | item: 9 | The name of the device 10 | 11 | inventory: 12 | One Service for each device 13 | -------------------------------------------------------------------------------- /share/check_mk/checkman/unifi_device: -------------------------------------------------------------------------------- 1 | title: Unifi Device 2 | agents: unifi_controller 3 | catalog: networking 4 | licence: MIT 5 | description: 6 | plz fill me 7 | 8 | item: 9 | The name of the device 10 | 11 | inventory: 12 | One Service for each device 13 | -------------------------------------------------------------------------------- /share/check_mk/checkman/unifi_device_shortlist: -------------------------------------------------------------------------------- 1 | title: Unifi Devicelist 2 | catalog: networking 3 | agents: unifi_controller 4 | licence: MIT 5 | description: 6 | plz fill me 7 | 8 | item: 9 | The name of the device 10 | 11 | inventory: 12 | One Service for each device 13 | -------------------------------------------------------------------------------- /share/check_mk/checkman/unifi_network_ports_if: -------------------------------------------------------------------------------- 1 | title: Unifi Network Port 2 | agents: unifi_controller 3 | catalog: networking 4 | licence: MIT 5 | description: 6 | plz fill me 7 | 8 | item: 9 | The name of the Port 10 | 11 | inventory: 12 | One Service for each Port 13 | -------------------------------------------------------------------------------- /share/check_mk/checkman/unifi_network_radios: -------------------------------------------------------------------------------- 1 | title: Unifi WLAN Radio 2 | agents: unifi_controller 3 | catalog: networking 4 | licence: MIT 5 | description: 6 | plz fill me 7 | 8 | item: 9 | The name of the Radio 10 | 11 | inventory: 12 | One Service for each Radio 13 | -------------------------------------------------------------------------------- /share/check_mk/checkman/unifi_network_ssids: -------------------------------------------------------------------------------- 1 | title: Unifi SSID 2 | agents: unifi_controller 3 | catalog: networking 4 | licence: MIT 5 | description: 6 | plz fill me 7 | 8 | item: 9 | The name of the SSID 10 | 11 | inventory: 12 | One Service for each SSID 13 | -------------------------------------------------------------------------------- /share/check_mk/checkman/unifi_sites: -------------------------------------------------------------------------------- 1 | title: Unifi Site 2 | agents: unifi_controller 3 | catalog: networking 4 | licence: MIT 5 | description: 6 | plz fill me 7 | 8 | item: 9 | The name of the Site 10 | 11 | inventory: 12 | One Service for each Site 13 | -------------------------------------------------------------------------------- /share/check_mk/checkman/unifi_ssid_list: -------------------------------------------------------------------------------- 1 | title: Unifi Devicelist 2 | catalog: networking 3 | agents: unifi_controller 4 | licence: MIT 5 | description: 6 | plz fill me 7 | 8 | item: 9 | The name of the device 10 | 11 | inventory: 12 | One Service for each device 13 | -------------------------------------------------------------------------------- /share/check_mk/checks/agent_unifi_controller: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf-8; py-indent-offset: 4 -*- 3 | ## MIT License 4 | ## 5 | ## Copyright (c) 2021 Bash Club 6 | ## 7 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 8 | ## of this software and associated documentation files (the "Software"), to deal 9 | ## in the Software without restriction, including without limitation the rights 10 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ## copies of the Software, and to permit persons to whom the Software is 12 | ## furnished to do so, subject to the following conditions: 13 | ## 14 | ## The above copyright notice and this permission notice shall be included in all 15 | ## copies or substantial portions of the Software. 16 | ## 17 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | ## SOFTWARE. 24 | 25 | #Function get params (in this case is port, passed via WATO agent rule cunfiguration, hostname and ip addres of host, 26 | #for which agent will be invoked 27 | def agent_unifi_controller_arguments(params, hostname, ipaddress): 28 | args = [ 29 | '--user', params['user'], 30 | '--password', passwordstore_get_cmdline('%s', params['password']), 31 | '--port', params['port'], 32 | '--piggyback',params['piggyback'], 33 | ] 34 | _site = params.get("site") 35 | if _site: 36 | args += ["--site",_site] 37 | if 'ignore_cert' in params and params['ignore_cert'] != '': 38 | args += ['--ignore-cert'] 39 | args += [ipaddress] 40 | return args 41 | 42 | 43 | #register invoke function for our agent 44 | #key value for this dictionary is name part from register datasource of our agent (name="special_agents:myspecial") 45 | special_agent_info['unifi_controller'] = agent_unifi_controller_arguments 46 | 47 | 48 | -------------------------------------------------------------------------------- /share/check_mk/inventory/unifi_controller: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf-8; py-indent-offset: 4 -*- 3 | 4 | from pprint import pprint 5 | from collections import defaultdict 6 | 7 | class dictobject(defaultdict): 8 | def __getattr__(self,name): 9 | return self[name] if name in self else "" 10 | 11 | nested_dictobject = lambda: dictobject(nested_dictobject) 12 | 13 | def inv_unifi_controller(info): 14 | node = inv_tree("software.os") 15 | node["version"] = info.get("controller_version") 16 | 17 | def inv_unifi_device(info): 18 | node = inv_tree("software.configuration.snmp_info") 19 | node["name"] = info.get("name") 20 | node["contact"] = info.get("snmp_contact") 21 | node["location"] = info.get("snmp_location") 22 | node = inv_tree("software.os") 23 | node["version"] = info.get("version") 24 | node = inv_tree("harware.system") 25 | node["vendor"] = "ubiquiti" 26 | for _key in ("model","board_rev","serial","mac"): 27 | _val = info.get(_key) 28 | if _val: 29 | node[_key] = _val 30 | 31 | def inv_unifi_port(info,params,inventory_tree): 32 | _parsed = nested_dictobject() 33 | for _line in info: 34 | _parsed[_line[0]][_line[1]] = _line[2] 35 | 36 | _interfaces = [] 37 | _total_ethernet_ports = 0 38 | _available_ethernet_ports = 0 39 | def _saveint(num): 40 | try: 41 | return int(num) 42 | except (TypeError,ValueError): 43 | return 0 44 | for _iface in _parsed.values(): 45 | _interfaces.append({ 46 | "index" : int(_iface.port_idx), 47 | "description" : _iface.name, 48 | "alias" : _iface.name, 49 | "speed" : _saveint(_iface.speed)*1000000, 50 | "phys_address" : "", 51 | "oper_status" : _saveint(_iface.oper_status), 52 | "admin_status" : _saveint(_iface.admin_status), 53 | "port_type" : 6, 54 | "available" : _iface.oper_status == '2' 55 | }) 56 | _total_ethernet_ports+=1 57 | _available_ethernet_ports+=1 if _iface.oper_status == '2' else 0 58 | 59 | node = inventory_tree.get_list("networking.interfaces:") 60 | node.extend(sorted(_interfaces, key=lambda i: i.get('index'))) 61 | node = inventory_tree.get_dict("networking.") 62 | node["available_ethernet_ports"] = _available_ethernet_ports 63 | node["total_ethernet_ports"] = _total_ethernet_ports 64 | node["total_interfaces"] = len(_parsed) 65 | 66 | inv_info["unifi_controller"] = { 67 | "inv_function" : inv_unifi_controller 68 | } 69 | inv_info["unifi_device"] = { 70 | "inv_function" : inv_unifi_device 71 | } 72 | 73 | inv_info["unifi_network_ports"] = { 74 | "inv_function" : inv_unifi_port 75 | } 76 | 77 | -------------------------------------------------------------------------------- /share/check_mk/web/plugins/metrics/unifi_metrics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf-8; py-indent-offset: 4 -*- 3 | ## MIT License 4 | ## 5 | ## Copyright (c) 2021 Bash Club 6 | ## 7 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 8 | ## of this software and associated documentation files (the "Software"), to deal 9 | ## in the Software without restriction, including without limitation the rights 10 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ## copies of the Software, and to permit persons to whom the Software is 12 | ## furnished to do so, subject to the following conditions: 13 | ## 14 | ## The above copyright notice and this permission notice shall be included in all 15 | ## copies or substantial portions of the Software. 16 | ## 17 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | ## SOFTWARE. 24 | 25 | from cmk.gui.i18n import _ 26 | from cmk.gui.plugins.metrics import ( 27 | metric_info, 28 | graph_info, 29 | translation 30 | ) 31 | 32 | 33 | # Colors: 34 | # 35 | # red 36 | # magenta orange 37 | # 11 12 13 14 15 16 38 | # 46 21 39 | # 45 22 40 | # blue 44 23 yellow 41 | # 43 24 42 | # 42 25 43 | # 41 26 44 | # 36 35 34 33 32 31 45 | # cyan yellow-green 46 | # green 47 | # 48 | # Special colors: 49 | # 51 gray 50 | # 52 brown 1 51 | # 53 brown 2 52 | # 53 | # For a new metric_info you have to choose a color. No more hex-codes are needed! 54 | # Instead you can choose a number of the above color ring and a letter 'a' or 'b 55 | # where 'a' represents the basic color and 'b' is a nuance/shading of the basic color. 56 | # Both number and letter must be declared! 57 | # 58 | # Example: 59 | # "color" : "23/a" (basic color yellow) 60 | # "color" : "23/b" (nuance of color yellow) 61 | # 62 | 63 | 64 | metric_info["satisfaction"] = { 65 | "title": _("Satisfaction"), 66 | "unit": "%", 67 | "color": "16/a", 68 | } 69 | 70 | metric_info["poe_current"] = { 71 | "title": _("PoE Current"), 72 | "unit": "a", 73 | "color": "16/a", 74 | } 75 | metric_info["poe_voltage"] = { 76 | "title": _("PoE Voltage"), 77 | "unit": "v", 78 | "color": "12/a", 79 | } 80 | metric_info["poe_power"] = { 81 | "title": _("PoE Power"), 82 | "unit": "w", 83 | "color": "16/a", 84 | } 85 | 86 | metric_info["user_sta"] = { 87 | "title": _("User"), 88 | "unit": "", 89 | "color": "13/b", 90 | } 91 | metric_info["guest_sta"] = { 92 | "title": _("Guest"), 93 | "unit": "", 94 | "color": "13/a", 95 | } 96 | 97 | graph_info["user_sta_combined"] = { 98 | "title" : _("User"), 99 | "metrics" : [ 100 | ("user_sta","area"), 101 | ("guest_sta","stack"), 102 | ], 103 | } 104 | 105 | metric_info["lan_user_sta"] = { 106 | "title": _("LAN User"), 107 | "unit": "count", 108 | "color": "13/b", 109 | } 110 | metric_info["lan_guest_sta"] = { 111 | "title": _("LAN Guest"), 112 | "unit": "count", 113 | "color": "13/a", 114 | } 115 | 116 | graph_info["lan_user_sta_combined"] = { 117 | "title" : _("LAN-User"), 118 | "metrics" : [ 119 | ("lan_user_sta","area"), 120 | ("lan_guest_sta","stack"), 121 | ], 122 | } 123 | 124 | 125 | metric_info["lan_active_sw"] = { 126 | "title": _("Active Switches"), 127 | "unit": "count", 128 | "color": "13/b", 129 | } 130 | metric_info["lan_total_sw"] = { 131 | "title": _("Total Switches"), 132 | "unit": "count", 133 | "color": "13/a", 134 | } 135 | 136 | graph_info["lan_active_sw_combined"] = { 137 | "title" : _("Active Switches"), 138 | "metrics" : [ 139 | ("lan_active_sw","area"), 140 | ("lan_total_sw","line"), 141 | ], 142 | } 143 | metric_info["wlan_active_ap"] = { 144 | "title": _("Active Accesspoints"), 145 | "unit": "count", 146 | "color": "13/b", 147 | } 148 | metric_info["wlan_total_ap"] = { 149 | "title": _("Total Accesspoints"), 150 | "unit": "count", 151 | "color": "13/a", 152 | } 153 | 154 | graph_info["wlan_active_ap_combined"] = { 155 | "title" : _("Active Accesspoints"), 156 | "metrics" : [ 157 | ("wlan_active_ap","area"), 158 | ("wlan_total_ap","line"), 159 | ], 160 | } 161 | 162 | metric_info["wlan_user_sta"] = { 163 | "title": _("WLAN User"), 164 | "unit": "count", 165 | "color": "13/b", 166 | } 167 | metric_info["wlan_guest_sta"] = { 168 | "title": _("WLAN Guest"), 169 | "unit": "count", 170 | "color": "13/a", 171 | } 172 | metric_info["wlan_iot_sta"] = { 173 | "title": _("WLAN IoT Devices"), 174 | "unit": "count", 175 | "color": "14/a", 176 | } 177 | 178 | graph_info["wlan_user_sta_combined"] = { 179 | "title" : _("WLAN-User"), 180 | "metrics" : [ 181 | ("wlan_user_sta","area"), 182 | ("wlan_guest_sta","stack"), 183 | ("wlan_iot_sta","stack"), 184 | ], 185 | } 186 | 187 | metric_info["wlan_24Ghz_num_user"] = { 188 | "title": _("User 2.4Ghz"), 189 | "unit": "count", 190 | "color": "13/b", 191 | } 192 | metric_info["wlan_5Ghz_num_user"] = { 193 | "title": _("User 5Ghz"), 194 | "unit": "count", 195 | "color": "13/a", 196 | } 197 | 198 | graph_info["wlan_user_band_combined"] = { 199 | "title" : _("WLAN User"), 200 | "metrics" : [ 201 | ("wlan_24Ghz_num_user","area"), 202 | ("wlan_5Ghz_num_user","stack"), 203 | ], 204 | } 205 | 206 | #na_avg_client_signal 207 | #ng_avg_client_signal 208 | 209 | 210 | metric_info["wlan_if_in_octets"] = { 211 | "title": _("Input Octets"), 212 | "unit": "bytes/s", 213 | "color": "#00e060", 214 | } 215 | metric_info["wlan_if_out_octets"] = { 216 | "title": _("Output Octets"), 217 | "unit": "bytes/s", 218 | "color": "#00e060", 219 | } 220 | graph_info["wlan_bandwidth_translated"] = { 221 | "title": _("Bandwidth WLAN"), 222 | "metrics": [ 223 | ("wlan_if_in_octets,8,*@bits/s", "area", _("Input bandwidth")), 224 | ("wlan_if_out_octets,8,*@bits/s", "-area", _("Output bandwidth")), 225 | ], 226 | "scalars": [ 227 | ("if_in_octets:warn", _("Warning (In)")), 228 | ("if_in_octets:crit", _("Critical (In)")), 229 | ("if_out_octets:warn,-1,*", _("Warning (Out)")), 230 | ("if_out_octets:crit,-1,*", _("Critical (Out)")), 231 | ], 232 | } 233 | 234 | 235 | metric_info["na_avg_client_signal"] = { 236 | "title" :_("Average Signal 5Ghz"), 237 | "unit" : "db", 238 | "color" : "14/a", 239 | } 240 | metric_info["ng_avg_client_signal"] = { 241 | "title" :_("Average Signal 2.4Ghz"), 242 | "unit" : "db", 243 | "color" : "#80f000", 244 | } 245 | graph_info["avg_client_signal_combined"] = { 246 | "title" : _("Average Client Signal"), 247 | "metrics" : [ 248 | ("na_avg_client_signal","line"), 249 | ("ng_avg_client_signal","line"), 250 | ], 251 | "range" : (-100,0) 252 | } 253 | 254 | 255 | ## different unit ??? 256 | #graph_info["poe_usage_combined"] = { 257 | # "title" : _("PoE Usage"), 258 | # "metrics" : [ 259 | # ("poe_power","area"), 260 | # ("poe_voltage","line"), 261 | # ], 262 | #} 263 | 264 | ### fixme default uptime translation? 265 | metric_info["unifi_uptime"] = { 266 | "title" :_("Uptime"), 267 | "unit" : "s", 268 | "color" : "#80f000", 269 | } 270 | 271 | check_metrics["check_mk-unifi_network_ports_if"] = translation.if_translation 272 | -------------------------------------------------------------------------------- /share/check_mk/web/plugins/perfometer/unifi_performeter.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8; py-indent-offset: 4 -*- 2 | 3 | ## MIT License 4 | ## 5 | ## Copyright (c) 2021 Bash Club 6 | ## 7 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 8 | ## of this software and associated documentation files (the "Software"), to deal 9 | ## in the Software without restriction, including without limitation the rights 10 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ## copies of the Software, and to permit persons to whom the Software is 12 | ## furnished to do so, subject to the following conditions: 13 | ## 14 | ## The above copyright notice and this permission notice shall be included in all 15 | ## copies or substantial portions of the Software. 16 | ## 17 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | ## SOFTWARE. 24 | 25 | from cmk.gui.plugins.metrics import perfometer_info 26 | 27 | perfometer_info.append({ 28 | "type": "linear", 29 | "segments": ["satisfaction"], 30 | "total": 100.0, 31 | }) 32 | 33 | perfometer_info.append({ 34 | "type": "logarithmic", 35 | "metric": "unifi_uptime", 36 | "half_value": 2592000.0, 37 | "exponent": 2, 38 | }) 39 | 40 | -------------------------------------------------------------------------------- /share/check_mk/web/plugins/wato/datasource_unifi_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf-8; py-indent-offset: 4 -*- 3 | ## MIT License 4 | ## 5 | ## Copyright (c) 2021 Bash Club 6 | ## 7 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 8 | ## of this software and associated documentation files (the "Software"), to deal 9 | ## in the Software without restriction, including without limitation the rights 10 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ## copies of the Software, and to permit persons to whom the Software is 12 | ## furnished to do so, subject to the following conditions: 13 | ## 14 | ## The above copyright notice and this permission notice shall be included in all 15 | ## copies or substantial portions of the Software. 16 | ## 17 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | ## SOFTWARE. 24 | 25 | from cmk.gui.i18n import _ 26 | from cmk.gui.plugins.wato import ( 27 | HostRulespec, 28 | CheckParameterRulespecWithItem, 29 | IndividualOrStoredPassword, 30 | rulespec_registry, 31 | ) 32 | from cmk.gui.valuespec import ( 33 | Dictionary, 34 | Tuple, 35 | Alternative, 36 | NetworkPort, 37 | Checkbox, 38 | TextAscii, 39 | ) 40 | 41 | from cmk.gui.plugins.wato.datasource_programs import RulespecGroupDatasourceProgramsHardware 42 | 43 | def _valuespec_special_agent_unifi_controller(): 44 | return Dictionary( 45 | title = _('Unifi Controller via API'), 46 | help = _('This rule selects the Unifi API agent'), 47 | optional_keys=['site'], 48 | elements=[ 49 | ('user',TextAscii(title = _('API Username.'),allow_empty = False,)), 50 | ('password',IndividualOrStoredPassword(title = _('API password'),allow_empty = False,)), 51 | ('site',TextAscii(title = _('Site Name'),allow_empty = False,default_value='default')), ## optional but not empty 52 | ('port',NetworkPort(title = _('Port'),default_value = 443)), 53 | ('ignore_cert', Checkbox(title=_("Ignore certificate validation"), default_value=False)), 54 | ('piggyback', 55 | Alternative( 56 | title = _('Receive piggyback data by'), 57 | elements = [ 58 | FixedValue("name", title = _("Hostname")), 59 | FixedValue("ip", title = _("IP")), 60 | FixedValue("none", title = _("None")), 61 | ], 62 | default_value = "name" 63 | ) 64 | ) 65 | ] 66 | ) 67 | 68 | rulespec_registry.register( 69 | HostRulespec( 70 | group=RulespecGroupDatasourceProgramsHardware, 71 | #IMPORTANT, name must follow special_agents:, 72 | #where filename of our special agent located in path local/share/check_mk/agents/special/ is agent_ 73 | name='special_agents:unifi_controller', 74 | valuespec=_valuespec_special_agent_unifi_controller, 75 | )) 76 | 77 | def _item_spec_unifi_site(): 78 | return TextAscii( 79 | title=_("Site"), 80 | help=_("help Site Text") 81 | ) 82 | 83 | def _parameter_valuespec_unifi_site(): 84 | return Dictionary( 85 | title = _("Unifi Site"), 86 | optional_keys=[], 87 | elements = [ 88 | ('ignore_alarms', Checkbox(title=_("Ignore Site Alarms"), default_value=False)), 89 | ] 90 | ) 91 | 92 | rulespec_registry.register( 93 | CheckParameterRulespecWithItem( 94 | check_group_name = "unifi_sites", 95 | group=RulespecGroupCheckParametersNetworking, 96 | item_spec = _item_spec_unifi_site, 97 | match_type = "dict", 98 | parameter_valuespec=_parameter_valuespec_unifi_site, 99 | title=lambda: _("Unifi Site Parameter") 100 | ) 101 | ) 102 | --------------------------------------------------------------------------------