root / plugins / disk / snmp__areca_ @ 14e5261e
Historique | Voir | Annoter | Télécharger (7,64 ko)
| 1 | a7139bca | Lars Kruse | #!/usr/bin/env python |
|---|---|---|---|
| 2 | 7b989348 | Andreas Thienemann | |
| 3 | 227e5103 | Andreas Thienemann | # Copyright (C) 2009 - 2012 Andreas Thienemann <andreas@bawue.net> |
| 4 | 7b989348 | Andreas Thienemann | # |
| 5 | # This program is free software; you can redistribute it and/or modify |
||
| 6 | # it under the terms of the GNU Library General Public License as published by |
||
| 7 | # the Free Software Foundation; version 2 only |
||
| 8 | # |
||
| 9 | # This program is distributed in the hope that it will be useful, |
||
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 12 | # GNU Library General Public License for more details. |
||
| 13 | # |
||
| 14 | # You should have received a copy of the GNU Library General Public License |
||
| 15 | # along with this program; if not, write to the Free Software |
||
| 16 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
||
| 17 | # |
||
| 18 | |||
| 19 | """ |
||
| 20 | =head1 NAME |
||
| 21 | |||
| 22 | snmp__areca_ - Munin plugin to get temperature, voltage or fan speed values |
||
| 23 | from Areca network enabled RAID controllers via SNMP. |
||
| 24 | |||
| 25 | =head1 APPLICABLE SYSTEMS |
||
| 26 | |||
| 27 | All machines with a SNMP capable ARECA raid controller. |
||
| 28 | |||
| 29 | =head1 CONFIGURATION |
||
| 30 | |||
| 31 | Make sure your Areca controller is accessible via SNMP from the munin host: |
||
| 32 | snmpwalk -v 1 -c snmp_community ip.add.re.ss |
||
| 33 | |||
| 34 | The plugin is a wildcard plugin and can thus be used to retrieve different |
||
| 35 | values depending on the name of the file. |
||
| 36 | |||
| 37 | Linking it as snmp_10.8.1.230_areca_fan would retrieve the fan speed values from |
||
| 38 | the Areca controller at 10.8.1.230. |
||
| 39 | |||
| 40 | Valid values are fan, temp and volt. |
||
| 41 | |||
| 42 | Add the following to your /etc/munin/plugin-conf.d/snmp__areca file: |
||
| 43 | |||
| 44 | [snmp_ip.add.re.ss_*] |
||
| 45 | community private |
||
| 46 | version 1 |
||
| 47 | |||
| 48 | Then test the plugin by calling the following commands: |
||
| 49 | |||
| 50 | munin-run snmp_10.8.1.230_areca_temp config |
||
| 51 | munin-run snmp_10.8.1.230_areca_temp |
||
| 52 | |||
| 53 | Both commands should output sensible data without failing. |
||
| 54 | |||
| 55 | =head1 INTERPRETATION |
||
| 56 | |||
| 57 | The plugin shows the temperature in Celsius or the fanspeed in rotations per minute. |
||
| 58 | |||
| 59 | =head1 MAGIC MARKERS |
||
| 60 | |||
| 61 | #%# family=contrib |
||
| 62 | #%# capabilities= |
||
| 63 | |||
| 64 | =head1 VERSION |
||
| 65 | |||
| 66 | 0.0.1 |
||
| 67 | |||
| 68 | =head1 BUGS |
||
| 69 | |||
| 70 | None known. If you know of any, please raise a ticket at https://trac.bawue.org/munin/wiki/areca__snmp_ |
||
| 71 | |||
| 72 | =head1 AUTHOR |
||
| 73 | |||
| 74 | Andreas Thienemann <andreas@bawue.net> |
||
| 75 | |||
| 76 | =head1 LICENSE |
||
| 77 | |||
| 78 | GPLv2 |
||
| 79 | |||
| 80 | =cut |
||
| 81 | """ |
||
| 82 | |||
| 83 | import pprint |
||
| 84 | import time |
||
| 85 | import sys |
||
| 86 | import re |
||
| 87 | import os |
||
| 88 | from pysnmp import v1, v2c, role, asn1 |
||
| 89 | |||
| 90 | request_conf = {
|
||
| 91 | "volt" : {
|
||
| 92 | "label" : "Voltages", |
||
| 93 | "vlabel" : "Volt", |
||
| 94 | "graph" : "--base 1000 --logarithmic", |
||
| 95 | "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.8" |
||
| 96 | }, |
||
| 97 | "fan" : {
|
||
| 98 | "label" : "Fans", |
||
| 99 | "vlabel" : "RPM", |
||
| 100 | "graph" : "--base 1000 -l 0", |
||
| 101 | "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.9" |
||
| 102 | }, |
||
| 103 | "temp" : {
|
||
| 104 | "label" : "Temperatures", |
||
| 105 | "vlabel" : "Celsius", |
||
| 106 | "graph" : "--base 1000 -l 0", |
||
| 107 | "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.10" |
||
| 108 | } |
||
| 109 | } |
||
| 110 | |||
| 111 | # Sanity check and parsing of the commandline |
||
| 112 | host = None |
||
| 113 | port = 161 |
||
| 114 | request = None |
||
| 115 | try: |
||
| 116 | match = re.search("^(?:|.*\/)snmp_([^_]+)_areca_(.+)$", sys.argv[0])
|
||
| 117 | host = match.group(1) |
||
| 118 | request = match.group(2) |
||
| 119 | match = re.search("^([^:]+):(\d+)$", host)
|
||
| 120 | if match is not None: |
||
| 121 | host = match.group(1) |
||
| 122 | port = match.group(2) |
||
| 123 | except: |
||
| 124 | pass |
||
| 125 | |||
| 126 | if host is None or request is None: |
||
| 127 | print "# Error: Incorrect filename. Cannot parse host or request." |
||
| 128 | sys.exit(1) |
||
| 129 | |||
| 130 | # Parse env variables |
||
| 131 | if os.getenv("community") is not None:
|
||
| 132 | community = os.getenv("community")
|
||
| 133 | else: |
||
| 134 | community = "public" |
||
| 135 | if os.getenv("version") is not None:
|
||
| 136 | version = os.getenv("version")
|
||
| 137 | else: |
||
| 138 | version = "1" |
||
| 139 | |||
| 140 | |||
| 141 | def get_data(): |
||
| 142 | # Fetch the data |
||
| 143 | results = snmpwalk(request_conf[request]["oid"]) |
||
| 144 | |||
| 145 | # parse data |
||
| 146 | vals = [] |
||
| 147 | for i in range(0, len(results)): |
||
| 148 | idx, res = results[i][0].split(request_conf[request]["oid"])[1].split(".")[1:], results[i][1]
|
||
| 149 | if idx[1] == '1': |
||
| 150 | vals.append([]) |
||
| 151 | vals[int(idx[2])-1].append(res) |
||
| 152 | if idx[1] == '2': |
||
| 153 | vals[int(idx[2])-1].append(res) |
||
| 154 | if idx[1] == '3': |
||
| 155 | if request == "volt": |
||
| 156 | res = float(res)/1000 |
||
| 157 | vals[int(idx[2])-1].append(res) |
||
| 158 | |||
| 159 | return vals |
||
| 160 | |||
| 161 | def snmpwalk(root): |
||
| 162 | |||
| 163 | # Create SNMP manager object |
||
| 164 | client = role.manager((host, port)) |
||
| 165 | |||
| 166 | # Create a SNMP request&response objects from protocol version |
||
| 167 | # specific module. |
||
| 168 | try: |
||
| 169 | req = eval('v' + version).GETREQUEST()
|
||
| 170 | nextReq = eval('v' + version).GETNEXTREQUEST()
|
||
| 171 | rsp = eval('v' + version).GETRESPONSE()
|
||
| 172 | |||
| 173 | except (NameError, AttributeError): |
||
| 174 | print '# Unsupported SNMP protocol version: %s\n%s' % (version, usage) |
||
| 175 | sys.exit(-1) |
||
| 176 | |||
| 177 | # Store tables headers |
||
| 178 | head_oids = [root] |
||
| 179 | |||
| 180 | encoded_oids = map(asn1.OBJECTID().encode, head_oids) |
||
| 181 | |||
| 182 | result = []; |
||
| 183 | |||
| 184 | while 1: |
||
| 185 | |||
| 186 | # Encode OIDs, encode SNMP request message and try to send |
||
| 187 | # it to SNMP agent and receive a response |
||
| 188 | (answer, src) = client.send_and_receive(req.encode(community=community, encoded_oids=encoded_oids)) |
||
| 189 | |||
| 190 | # Decode SNMP response |
||
| 191 | rsp.decode(answer) |
||
| 192 | 17f78427 | Lars Kruse | |
| 193 | 7b989348 | Andreas Thienemann | # Make sure response matches request (request IDs, communities, etc) |
| 194 | if req != rsp: |
||
| 195 | raise 'Unmatched response: %s vs %s' % (str(req), str(rsp)) |
||
| 196 | |||
| 197 | # Decode BER encoded Object IDs. |
||
| 198 | oids = map(lambda x: x[0], map(asn1.OBJECTID().decode, rsp['encoded_oids'])) |
||
| 199 | |||
| 200 | # Decode BER encoded values associated with Object IDs. |
||
| 201 | vals = map(lambda x: x[0](), map(asn1.decode, rsp['encoded_vals'])) |
||
| 202 | |||
| 203 | # Check for remote SNMP agent failure |
||
| 204 | if rsp['error_status']: |
||
| 205 | # SNMP agent reports 'no such name' when walk is over |
||
| 206 | if rsp['error_status'] == 2: |
||
| 207 | # Switch over to GETNEXT req on error |
||
| 208 | # XXX what if one of multiple vars fails? |
||
| 209 | if not (req is nextReq): |
||
| 210 | req = nextReq |
||
| 211 | continue |
||
| 212 | # One of the tables exceeded |
||
| 213 | for l in oids, vals, head_oids: |
||
| 214 | del l[rsp['error_index']-1] |
||
| 215 | else: |
||
| 216 | raise 'SNMP error #' + str(rsp['error_status']) + ' for OID #' + str(rsp['error_index']) |
||
| 217 | |||
| 218 | # Exclude completed OIDs |
||
| 219 | while 1: |
||
| 220 | for idx in range(len(head_oids)): |
||
| 221 | if not asn1.OBJECTID(head_oids[idx]).isaprefix(oids[idx]): |
||
| 222 | # One of the tables exceeded |
||
| 223 | for l in oids, vals, head_oids: |
||
| 224 | del l[idx] |
||
| 225 | break |
||
| 226 | else: |
||
| 227 | break |
||
| 228 | |||
| 229 | if not head_oids: |
||
| 230 | return result |
||
| 231 | |||
| 232 | # Print out results |
||
| 233 | for (oid, val) in map(None, oids, vals): |
||
| 234 | result.append((oid, str(val))) |
||
| 235 | # print oid + ' ---> ' + str(val) |
||
| 236 | |||
| 237 | # BER encode next SNMP Object IDs to query |
||
| 238 | encoded_oids = map(asn1.OBJECTID().encode, oids) |
||
| 239 | |||
| 240 | # Update request object |
||
| 241 | req['request_id'] = req['request_id'] + 1 |
||
| 242 | |||
| 243 | # Switch over GETNEXT PDU for if not done |
||
| 244 | if not (req is nextReq): |
||
| 245 | req = nextReq |
||
| 246 | |||
| 247 | raise "error" |
||
| 248 | |||
| 249 | |||
| 250 | def print_config(): |
||
| 251 | print "graph_title " + request_conf[request]["label"] |
||
| 252 | print "graph_vlabel " + request_conf[request]["vlabel"] |
||
| 253 | print "graph_args " + request_conf[request]["graph"] |
||
| 254 | print "graph_category sensors" |
||
| 255 | print "host_name", host |
||
| 256 | for dataset in get_data(): |
||
| 257 | if request == "volt": |
||
| 258 | if dataset[1] == "Battery Status": |
||
| 259 | continue |
||
| 260 | else: |
||
| 261 | print request + dataset[0] + ".label", dataset[1] |
||
| 262 | ref_val = float(dataset[1].split()[-1][:-1]) |
||
| 263 | print request + dataset[0] + ".warning", str(ref_val * 0.95) + ":" + str(ref_val * 1.05) |
||
| 264 | print request + dataset[0] + ".critical", str(ref_val * 0.80) + ":" + str(ref_val * 1.20) |
||
| 265 | if request == "temp": |
||
| 266 | print request + dataset[0] + ".label", dataset[1] |
||
| 267 | if dataset[1].startswith("CPU"):
|
||
| 268 | print request + dataset[0] + ".warning", 55 |
||
| 269 | print request + dataset[0] + ".critical", 60 |
||
| 270 | if dataset[1].startswith("System"):
|
||
| 271 | print request + dataset[0] + ".warning", 40 |
||
| 272 | print request + dataset[0] + ".critical", 45 |
||
| 273 | if request == "fan": |
||
| 274 | print request + dataset[0] + ".label", dataset[1] |
||
| 275 | print request + dataset[0] + ".warning", 2400 |
||
| 276 | print request + dataset[0] + ".critical", 2000 |
||
| 277 | |||
| 278 | sys.exit(0) |
||
| 279 | |||
| 280 | |||
| 281 | if "config" in sys.argv[1:]: |
||
| 282 | print_config() |
||
| 283 | elif "snmpconf" in sys.argv[1:]: |
||
| 284 | print "require 1.3.6.1.4.1.18928.1.2.2.1.8.1.1" |
||
| 285 | sys.exit(0) |
||
| 286 | else: |
||
| 287 | for dataset in get_data(): |
||
| 288 | # Filter Battery Status (255 == Not installed) |
||
| 289 | if request == "volt" and dataset[1] == "Battery Status": |
||
| 290 | continue |
||
| 291 | print request + dataset[0] + ".value", dataset[2] |
||
| 292 | sys.exit(0) |
