Skip to content

Commit

Permalink
Merge pull request zabbix#148 from blind-oracle/add_template_nut
Browse files Browse the repository at this point in the history
Add template_nut (network ups tools)
  • Loading branch information
oscar120584 authored Sep 21, 2022
2 parents 182ca84 + eeae365 commit 354bff6
Show file tree
Hide file tree
Showing 8 changed files with 1,107 additions and 0 deletions.
87 changes: 87 additions & 0 deletions Power_(UPS)/template_nut/5.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# zabbix-nut

Zabbix template & script to monitor UPS exposed through NUT (https://networkupstools.org).

Currently only basic parameters are gathered (which are available on my UPS). Feel free to do PR to add other stuff, preferably in form of low-level discovery so that if it's not available - items are not created.

Single Python script that emits all information needed for discovery & data gathering in a single JSON.
All items are defined as `Dependent` and extract relevant data using JSONPath queries.

<details>
<summary>Click to expand JSON example</summary>

```json
{
"vars": {
"ups": {
"battery.charge": 100,
"battery.voltage": 27.7,
"battery.voltage.high": 27.7,
"battery.voltage.low": 21,
"battery.voltage.nominal": 24.0,
"device.mfr": "",
"device.model": "1500VA",
"device.type": "ups",
"driver.name": "blazer_usb",
"driver.parameter.pollinterval": 2,
"driver.parameter.port": "auto",
"driver.parameter.synchronous": "no",
"driver.version": "2.7.4",
"driver.version.internal": 0.12,
"input.current.nominal": 6.0,
"input.frequency": 50.0,
"input.frequency.nominal": 50,
"input.voltage": 221.7,
"input.voltage.fault": 0.5,
"input.voltage.nominal": 230,
"output.voltage": 223.0,
"ups.beeper.status": "enabled",
"ups.delay.shutdown": 30,
"ups.delay.start": 180,
"ups.firmware": "",
"ups.load": 26,
"ups.mfr": "",
"ups.model": "1500VA",
"ups.productid": 5161,
"ups.status": "OL",
"ups.type": "offline / line interactive",
"ups.vendorid": 665
}
},
"list": [
{
"name": "ups"
}
]
}
```
</details>

## Features

- Low level discovery of UPS
- Items:
- Voltage & Frequency
- Battery voltage & charge
- UPS Status
- Triggers:
- Voltage out of bounds
- UPS status is not healthy
- UPS is calibrating/in bypass/charging
- Optional authentication (fill `{$NUT_USERNAME}` and `{$NUT_PASSWORD}` macros to enable)
- Zabbix agent passive checks. Can be converted to active if needed.
- Macros to customize triggers

## Requirements

- Tested on Zabbix 5.2, but should work on 4.2+
- Python3

## Installation

- Place `nut.conf` in `/etc/zabbix/zabbix_agentd.d`
- Place `nut.py` in `/etc/zabbix/scripts`
You can put it into any other place, but then you'll have to adjust `nut.conf`
- Restart `zabbix-agentd`
- Import `template_nut.xml`
- You're good to go
1 change: 1 addition & 0 deletions Power_(UPS)/template_nut/5.0/files/nut.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
UserParameter=nut[*],/etc/zabbix/scripts/nut.py $1 $2 $3 $4
90 changes: 90 additions & 0 deletions Power_(UPS)/template_nut/5.0/files/nut.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python3

import sys
import socket
import json


username, password = "", ""

argc = len(sys.argv)

if argc in (3, 5):
host, port = sys.argv[1], int(sys.argv[2])
if argc == 5:
username, password = sys.argv[3], sys.argv[4]
else:
print(f"Usage: {sys.argv[0]} <host> <port> [<user> <pass>]")
sys.exit(1)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
sock.settimeout(5)


def conv(s):
try:
s = int(s)
except BaseException:
try:
s = float(s)
except BaseException:
return s

return s


def read_reply(cmd):
sock.sendall(str.encode(cmd + "\n"))
r = []

while True:
s = sock.recv(8192).decode('ascii')

for l in s.split("\n"):
if l == "" or l.startswith("BEGIN"):
continue
elif l.startswith("END"):
return r

r.append(l)


def send_cmd(cmd, expect):
sock.sendall(str.encode(cmd))
r = sock.recv(1024).decode('ascii')
if r != expect:
raise Exception(f"{r} != {expect}\n")


def list_ups():
ups = []
for l in read_reply("LIST UPS"):
t = l.split(" ", 2)
ups.append(t[1])
return ups


def read_vars(ups):
vars = {}
for l in read_reply(f"LIST VAR {ups}"):
t = l.split(" ", 3)
vars[t[2]] = conv(t[3][1:len(t[3]) - 1])
return vars


if username != "":
send_cmd(f"USERNAME {username}\n", "OK\n")
send_cmd(f"PASSWORD {password}\n", "OK\n")

r = {'vars': {}}
ups_list = list_ups()
r['list'] = [{'name': x} for x in ups_list]

for u in ups_list:
r['vars'][u] = read_vars(u)

sock.sendall(b'LOGOUT\n')
sock.close()

json.dump(r, sys.stdout, indent=1)
Loading

0 comments on commit 354bff6

Please sign in to comment.