Refactoring: Changed all remaining check parameters to the new rulespec registry...
[check_mk.git] / cmk_base / snmp.py
blob4787f8bdd4ca44849a180cbc2cf0dc89cce28433
1 #!/usr/bin/python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 import os
28 import subprocess
29 from typing import Any, Dict, List # pylint: disable=unused-import
31 import cmk.utils.debug
32 import cmk.utils.tty as tty
33 from cmk.utils.exceptions import MKGeneralException, MKBailOut
34 import cmk.utils.store as store
36 import cmk_base.utils
37 import cmk_base.config as config
38 import cmk_base.console as console
39 import cmk_base.classic_snmp as classic_snmp
40 import cmk_base.ip_lookup as ip_lookup
41 import cmk_base.agent_simulator
42 from cmk_base.exceptions import MKSNMPError
43 import cmk_base.cleanup
44 import cmk_base.snmp_utils
46 try:
47 import cmk_base.cee.inline_snmp as inline_snmp
48 except ImportError:
49 inline_snmp = None # type: ignore
51 _enforce_stored_walks = False
53 # TODO: Replace this by generic caching
54 _g_single_oid_hostname = None
55 _g_single_oid_ipaddress = None
56 _g_single_oid_cache = None
57 _g_walk_cache = {} # type: Dict[str, List[str]]
60 # .--caching-------------------------------------------------------------.
61 # | _ _ |
62 # | ___ __ _ ___| |__ (_)_ __ __ _ |
63 # | / __/ _` |/ __| '_ \| | '_ \ / _` | |
64 # | | (_| (_| | (__| | | | | | | | (_| | |
65 # | \___\__,_|\___|_| |_|_|_| |_|\__, | |
66 # | |___/ |
67 # '----------------------------------------------------------------------'
69 #TODO CACHING
72 def initialize_single_oid_cache(access_data, from_disk=False):
73 hostname = access_data["hostname"]
74 ipaddress = access_data["ipaddress"]
75 global _g_single_oid_cache, _g_single_oid_ipaddress, _g_single_oid_hostname
77 if not (_g_single_oid_hostname == hostname \
78 and _g_single_oid_ipaddress == ipaddress) \
79 or _g_single_oid_cache is None:
80 _g_single_oid_hostname = hostname
81 _g_single_oid_ipaddress = ipaddress
82 if from_disk:
83 _g_single_oid_cache = _load_single_oid_cache(access_data)
84 else:
85 _g_single_oid_cache = {}
88 def write_single_oid_cache(access_data):
89 if not _g_single_oid_cache:
90 return
92 cache_dir = cmk.utils.paths.snmp_scan_cache_dir
93 if not os.path.exists(cache_dir):
94 os.makedirs(cache_dir)
95 cache_path = "%s/%s.%s" % (cache_dir, access_data["hostname"], access_data["ipaddress"])
96 store.save_data_to_file(cache_path, _g_single_oid_cache, pretty=False)
99 def set_single_oid_cache(hostname, oid, value):
100 _g_single_oid_cache[oid] = value
103 def _is_in_single_oid_cache(hostname, oid):
104 return oid in _g_single_oid_cache
107 def _get_oid_from_single_oid_cache(hostname, oid):
108 return _g_single_oid_cache.get(oid)
111 def _load_single_oid_cache(access_data):
112 cache_path = "%s/%s.%s" % (cmk.utils.paths.snmp_scan_cache_dir, access_data["hostname"],
113 access_data["ipaddress"])
114 return store.load_data_from_file(cache_path, {})
117 def cleanup_host_caches():
118 global _g_walk_cache
119 _g_walk_cache = {}
120 _clear_other_hosts_oid_cache(None)
121 if inline_snmp:
122 inline_snmp.cleanup_inline_snmp_globals()
125 def _clear_other_hosts_oid_cache(hostname):
126 global _g_single_oid_cache, _g_single_oid_ipaddress, _g_single_oid_hostname
127 if _g_single_oid_hostname != hostname:
128 _g_single_oid_cache = None
129 _g_single_oid_hostname = hostname
130 _g_single_oid_ipaddress = None
134 # .--Generic SNMP--------------------------------------------------------.
135 # | ____ _ ____ _ _ __ __ ____ |
136 # | / ___| ___ _ __ ___ _ __(_) ___ / ___|| \ | | \/ | _ \ |
137 # | | | _ / _ \ '_ \ / _ \ '__| |/ __| \___ \| \| | |\/| | |_) | |
138 # | | |_| | __/ | | | __/ | | | (__ ___) | |\ | | | | __/ |
139 # | \____|\___|_| |_|\___|_| |_|\___| |____/|_| \_|_| |_|_| |
140 # | |
141 # +----------------------------------------------------------------------+
142 # | Top level functions to realize SNMP functionality for Check_MK. |
143 # '----------------------------------------------------------------------'
146 def get_snmp_table(access_data, check_plugin_name, oid_info, use_snmpwalk_cache):
147 hostname = access_data["hostname"]
148 # oid_info is either ( oid, columns ) or
149 # ( oid, suboids, columns )
150 # suboids is a list if OID-infixes that are put between baseoid
151 # and the columns and also prefixed to the index column. This
152 # allows to merge distinct SNMP subtrees with a similar structure
153 # to one virtual new tree (look into cmctc_temp for an example)
154 if len(oid_info) == 2:
155 oid, targetcolumns = oid_info
156 suboids = [None]
157 else:
158 oid, suboids, targetcolumns = oid_info
160 if not oid.startswith("."):
161 raise MKGeneralException("OID definition '%s' does not begin with ." % oid)
163 index_column = -1
164 index_format = None
165 info = []
166 for suboid in suboids:
167 columns = []
168 # Detect missing (empty columns)
169 max_len = 0
170 max_len_col = -1
172 for colno, column in enumerate(targetcolumns):
173 fetchoid, value_encoding = _compute_fetch_oid(oid, suboid, column)
175 # column may be integer or string like "1.5.4.2.3"
176 # if column is 0, we do not fetch any data from snmp, but use
177 # a running counter as index. If the index column is the first one,
178 # we do not know the number of entries right now. We need to fill
179 # in later. If the column is OID_STRING or OID_BIN we do something
180 # similar: we fill in the complete OID of the entry, either as
181 # string or as binary UTF-8 encoded number string
182 if column in [
183 cmk_base.snmp_utils.OID_END, cmk_base.snmp_utils.OID_STRING,
184 cmk_base.snmp_utils.OID_BIN, cmk_base.snmp_utils.OID_END_BIN,
185 cmk_base.snmp_utils.OID_END_OCTET_STRING
187 if index_column >= 0 and index_column != colno:
188 raise MKGeneralException(
189 "Invalid SNMP OID specification in implementation of check. "
190 "You can only use one of OID_END, OID_STRING, OID_BIN, OID_END_BIN and OID_END_OCTET_STRING."
192 index_column = colno
193 columns.append((fetchoid, [], "string"))
194 index_format = column
195 continue
197 rowinfo = _get_snmpwalk(access_data, check_plugin_name, oid, fetchoid, column,
198 use_snmpwalk_cache)
200 columns.append((fetchoid, rowinfo, value_encoding))
201 number_of_rows = len(rowinfo)
202 if number_of_rows > max_len:
203 max_len = number_of_rows
204 max_len_col = colno
206 if index_column != -1:
207 index_rows = []
208 # Take end-oids of non-index columns as indices
209 fetchoid, max_column, value_encoding = columns[max_len_col]
210 for o, _unused_value in max_column:
211 if index_format == cmk_base.snmp_utils.OID_END:
212 index_rows.append((o, _extract_end_oid(fetchoid, o)))
213 elif index_format == cmk_base.snmp_utils.OID_STRING:
214 index_rows.append((o, o))
215 elif index_format == cmk_base.snmp_utils.OID_BIN:
216 index_rows.append((o, _oid_to_bin(o)))
217 elif index_format == cmk_base.snmp_utils.OID_END_BIN:
218 index_rows.append((o, _oid_to_bin(_extract_end_oid(fetchoid, o))))
219 elif index_format == cmk_base.snmp_utils.OID_END_OCTET_STRING:
220 index_rows.append((o, _oid_to_bin(_extract_end_oid(fetchoid, o))[1:]))
221 else:
222 raise MKGeneralException("Invalid index format %s" % index_format)
224 index_encoding = columns[index_column][-1]
225 columns[index_column] = fetchoid, index_rows, index_encoding
227 # prepend suboid to first column
228 if suboid and columns:
229 fetchoid, first_column, value_encoding = columns[0]
230 new_first_column = []
231 for o, val in first_column:
232 new_first_column.append((o, str(suboid) + "." + str(val)))
233 columns[0] = fetchoid, new_first_column, value_encoding
235 # Here we have to deal with a nasty problem: Some brain-dead devices
236 # omit entries in some sub OIDs. This happens e.g. for CISCO 3650
237 # in the interfaces MIB with 64 bit counters. So we need to look at
238 # the OIDs and watch out for gaps we need to fill with dummy values.
239 new_columns = _sanitize_snmp_table_columns(columns)
241 # From all SNMP data sources (stored walk, classic SNMP, inline SNMP) we
242 # get normal python strings. But for Check_MK we need unicode strings now.
243 # Convert them by using the standard Check_MK approach for incoming data
244 sanitized_columns = _sanitize_snmp_encoding(hostname, new_columns)
246 info += _construct_snmp_table_of_rows(sanitized_columns)
248 return info
251 # Contextes can only be used when check_plugin_name is given.
252 def get_single_oid(access_data, oid, check_plugin_name=None, do_snmp_scan=True):
253 # New in Check_MK 1.1.11: oid can end with ".*". In that case
254 # we do a snmpgetnext and try to find an OID with the prefix
255 # in question. The *cache* is working including the X, however.
256 hostname = access_data["hostname"]
257 if oid[0] != '.':
258 if cmk.utils.debug.enabled():
259 raise MKGeneralException("OID definition '%s' does not begin with a '.'" % oid)
260 else:
261 oid = '.' + oid
263 # TODO: Use generic cache mechanism
264 if _is_in_single_oid_cache(hostname, oid):
265 console.vverbose(" Using cached OID %s: " % oid)
266 value = _get_oid_from_single_oid_cache(hostname, oid)
267 console.vverbose("%s%s%s%s\n" % (tty.bold, tty.green, value, tty.normal))
268 return value
270 console.vverbose(" Getting OID %s: " % oid)
271 if _enforce_stored_walks or config.is_usewalk_host(hostname):
272 walk = _get_stored_snmpwalk(hostname, oid)
273 # get_stored_snmpwalk returns all oids that start with oid but here
274 # we need an exact match
275 if len(walk) == 1 and oid == walk[0][0]:
276 value = walk[0][1]
277 elif oid.endswith(".*") and len(walk) > 0:
278 value = walk[0][1]
279 else:
280 value = None
282 else:
283 # get_single_oid() can only return a single value. When SNMPv3 is used with multiple
284 # SNMP contexts, all contextes will be queried until the first answer is received.
285 if check_plugin_name is not None and config.is_snmpv3_host(hostname):
286 snmp_contexts = _snmpv3_contexts_of(hostname, check_plugin_name)
287 else:
288 snmp_contexts = [None]
290 for context_name in snmp_contexts:
291 try:
292 if config.is_inline_snmp_host(hostname):
293 value = inline_snmp.get(
294 hostname,
295 oid,
296 ipaddress=access_data["ipaddress"],
297 context_name=context_name,
298 credentials=access_data["credentials"])
299 else:
300 value = classic_snmp.get(access_data, oid, context_name=context_name)
302 if value is not None:
303 break # Use first received answer in case of multiple contextes
304 except:
305 if cmk.utils.debug.enabled():
306 raise
307 value = None
309 if value is not None:
310 console.vverbose("%s%s%s%s\n" % (tty.bold, tty.green, value, tty.normal))
311 else:
312 console.vverbose("failed.\n")
314 set_single_oid_cache(hostname, oid, value)
315 return value
318 def walk_for_export(access_data, oid):
319 hostname = access_data["hostname"]
320 if config.is_inline_snmp_host(hostname):
321 rows = inline_snmp.walk(
322 hostname,
323 None,
324 oid,
325 ipaddress=access_data["ipaddress"],
326 credentials=access_data["credentials"])
327 return inline_snmp.convert_rows_for_stored_walk(rows)
329 return classic_snmp.walk(access_data, oid, hex_plain=True)
332 def enforce_use_stored_walks():
333 global _enforce_stored_walks
334 _enforce_stored_walks = True
338 # .--SNMP helpers--------------------------------------------------------.
339 # | ____ _ _ __ __ ____ _ _ |
340 # | / ___|| \ | | \/ | _ \ | |__ ___| |_ __ ___ _ __ ___ |
341 # | \___ \| \| | |\/| | |_) | | '_ \ / _ \ | '_ \ / _ \ '__/ __| |
342 # | ___) | |\ | | | | __/ | | | | __/ | |_) | __/ | \__ \ |
343 # | |____/|_| \_|_| |_|_| |_| |_|\___|_| .__/ \___|_| |___/ |
344 # | |_| |
345 # +----------------------------------------------------------------------+
346 # | Internal helpers for processing SNMP things |
347 # '----------------------------------------------------------------------'
350 def _oid_to_bin(oid):
351 return u"".join([unichr(int(p)) for p in oid.strip(".").split(".")])
354 def _extract_end_oid(prefix, complete):
355 return complete[len(prefix):].lstrip('.')
358 # sort OID strings numerically
359 def _oid_to_intlist(oid):
360 if oid:
361 return map(int, oid.split('.'))
363 return []
366 def _cmp_oids(o1, o2):
367 return cmp(_oid_to_intlist(o1), _oid_to_intlist(o2))
370 def _cmp_oid_pairs(pair1, pair2):
371 return cmp(_oid_to_intlist(pair1[0].lstrip('.')), _oid_to_intlist(pair2[0].lstrip('.')))
374 def _snmpv3_contexts_of(hostname, check_plugin_name):
375 for ty, rules in config.snmpv3_contexts_of(hostname):
376 if ty is None or ty == check_plugin_name:
377 return rules
378 return [None]
381 def _get_snmpwalk(access_data, check_plugin_name, oid, fetchoid, column, use_snmpwalk_cache):
382 hostname = access_data["hostname"]
383 is_cachable = _is_snmpwalk_cachable(column)
384 rowinfo = None
385 if is_cachable and use_snmpwalk_cache:
386 # Returns either the cached SNMP walk or None when nothing is cached
387 rowinfo = _get_cached_snmpwalk(hostname, fetchoid)
389 if rowinfo is None:
390 if _enforce_stored_walks or config.is_usewalk_host(hostname):
391 rowinfo = _get_stored_snmpwalk(hostname, fetchoid)
392 else:
393 rowinfo = _perform_snmpwalk(access_data, check_plugin_name, oid, fetchoid)
395 if is_cachable:
396 _save_snmpwalk_cache(hostname, fetchoid, rowinfo)
398 return rowinfo
401 def _perform_snmpwalk(access_data, check_plugin_name, base_oid, fetchoid):
402 hostname = access_data["hostname"]
403 added_oids = set([])
404 rowinfo = []
405 if config.is_snmpv3_host(hostname):
406 snmp_contexts = _snmpv3_contexts_of(hostname, check_plugin_name)
407 else:
408 snmp_contexts = [None]
410 for context_name in snmp_contexts:
411 if config.is_inline_snmp_host(hostname):
412 rows = inline_snmp.walk(
413 hostname,
414 check_plugin_name,
415 fetchoid,
416 base_oid,
417 context_name=context_name,
418 ipaddress=access_data["ipaddress"],
419 credentials=access_data["credentials"])
420 else:
421 rows = classic_snmp.walk(access_data, fetchoid, context_name=context_name)
423 # I've seen a broken device (Mikrotik Router), that broke after an
424 # update to RouterOS v6.22. It would return 9 time the same OID when
425 # .1.3.6.1.2.1.1.1.0 was being walked. We try to detect these situations
426 # by removing any duplicate OID information
427 if len(rows) > 1 and rows[0][0] == rows[1][0]:
428 console.vverbose(
429 "Detected broken SNMP agent. Ignoring duplicate OID %s.\n" % rows[0][0])
430 rows = rows[:1]
432 for row_oid, val in rows:
433 if row_oid in added_oids:
434 console.vverbose("Duplicate OID found: %s (%s)\n" % (row_oid, val))
435 else:
436 rowinfo.append((row_oid, val))
437 added_oids.add(row_oid)
439 return rowinfo
442 def _compute_fetch_oid(oid, suboid, column):
443 fetchoid = oid
444 value_encoding = "string"
446 if suboid:
447 fetchoid += "." + str(suboid)
449 if column != "":
450 if isinstance(column, tuple):
451 fetchoid += "." + str(column[1])
452 if column[0] == "binary":
453 value_encoding = "binary"
454 else:
455 fetchoid += "." + str(column)
457 return fetchoid, value_encoding
460 def _sanitize_snmp_encoding(hostname, columns):
461 decode_string_func = lambda s: _snmp_decode_string(hostname, s)
463 for index, (column, value_encoding) in enumerate(columns):
464 if value_encoding == "string":
465 columns[index] = map(decode_string_func, column)
466 else:
467 columns[index] = map(_snmp_decode_binary, column)
468 return columns
471 def _snmp_decode_string(hostname, text):
472 encoding = config.snmp_character_encoding_of(hostname)
473 if encoding:
474 return text.decode(encoding)
475 else:
476 # Try to determine the current string encoding. In case a UTF-8 decoding fails, we decode latin1.
477 try:
478 return text.decode('utf-8')
479 except:
480 return text.decode('latin1')
483 def _snmp_decode_binary(text):
484 return map(ord, text)
487 def _sanitize_snmp_table_columns(columns):
488 # First compute the complete list of end-oids appearing in the output
489 # by looping all results and putting the endoids to a flat list
490 endoids = []
491 for fetchoid, column, value_encoding in columns:
492 for o, value in column:
493 endoid = _extract_end_oid(fetchoid, o)
494 if endoid not in endoids:
495 endoids.append(endoid)
497 # The list needs to be sorted to prevent problems when the first
498 # column has missing values in the middle of the tree.
499 if not _are_ascending_oids(endoids):
500 endoids.sort(cmp=_cmp_oids)
501 need_sort = True
502 else:
503 need_sort = False
505 # Now fill gaps in columns where some endois are missing
506 new_columns = []
507 for fetchoid, column, value_encoding in columns:
508 # It might happen that end OIDs are not ordered. Fix the OID sorting to make
509 # it comparable to the already sorted endoids list. Otherwise we would get
510 # some mixups when filling gaps
511 if need_sort:
512 column.sort(cmp=_cmp_oid_pairs)
514 i = 0
515 new_column = []
516 # Loop all lines to fill holes in the middle of the list. All
517 # columns check the following lines for the correct endoid. If
518 # an endoid differs empty values are added until the hole is filled
519 for o, value in column:
520 eo = _extract_end_oid(fetchoid, o)
521 if len(column) != len(endoids):
522 while i < len(endoids) and endoids[i] != eo:
523 new_column.append("") # (beginoid + '.' +endoids[i], "" ) )
524 i += 1
525 new_column.append(value)
526 i += 1
528 # At the end check if trailing OIDs are missing
529 while i < len(endoids):
530 new_column.append("") # (beginoid + '.' +endoids[i], "") )
531 i += 1
532 new_columns.append((new_column, value_encoding))
534 return new_columns
537 def _are_ascending_oids(oid_list):
538 for a in range(len(oid_list) - 1):
539 if _cmp_oids(oid_list[a], oid_list[a + 1]) > 0: # == 0 should never happen
540 return False
541 return True
544 def _construct_snmp_table_of_rows(columns):
545 if not columns:
546 return []
548 # Now construct table by swapping X and Y.
549 new_info = []
550 for index in range(len(columns[0])):
551 row = [c[index] for c in columns]
552 new_info.append(row)
553 return new_info
556 def _is_snmpwalk_cachable(column):
557 return isinstance(column, tuple) and column[0] == "cached"
560 def _get_cached_snmpwalk(hostname, fetchoid):
561 path = _snmpwalk_cache_path(hostname, fetchoid)
562 try:
563 console.vverbose(" Loading %s from walk cache %s\n" % (fetchoid, path))
564 return store.load_data_from_file(path)
565 except:
566 if cmk.utils.debug.enabled():
567 raise
568 console.verbose(" Failed loading walk cache. Continue without it.\n" % path)
569 return None
572 def _save_snmpwalk_cache(hostname, fetchoid, rowinfo):
573 path = _snmpwalk_cache_path(hostname, fetchoid)
575 if not os.path.exists(os.path.dirname(path)):
576 os.makedirs(os.path.dirname(path))
578 console.vverbose(" Saving walk of %s to walk cache %s\n" % (fetchoid, path))
579 store.save_data_to_file(path, rowinfo, pretty=False)
582 def _snmpwalk_cache_path(hostname, fetchoid):
583 return os.path.join(cmk.utils.paths.var_dir, "snmp_cache", hostname, fetchoid)
586 def _get_stored_snmpwalk(hostname, oid):
587 if oid.startswith("."):
588 oid = oid[1:]
590 if oid.endswith(".*"):
591 oid_prefix = oid[:-2]
592 dot_star = True
593 else:
594 oid_prefix = oid
595 dot_star = False
597 path = cmk.utils.paths.snmpwalks_dir + "/" + hostname
599 console.vverbose(" Loading %s from %s\n" % (oid, path))
601 rowinfo = []
603 # New implementation: use binary search
604 def to_bin_string(oid):
605 try:
606 return tuple(map(int, oid.strip(".").split(".")))
607 except:
608 raise MKGeneralException("Invalid OID %s" % oid)
610 def compare_oids(a, b):
611 aa = to_bin_string(a)
612 bb = to_bin_string(b)
613 if len(aa) <= len(bb) and bb[:len(aa)] == aa:
614 result = 0
615 else:
616 result = cmp(aa, bb)
617 return result
619 if hostname in _g_walk_cache:
620 lines = _g_walk_cache[hostname]
621 else:
622 try:
623 lines = file(path).readlines()
624 except IOError:
625 raise MKSNMPError("No snmpwalk file %s" % path)
626 _g_walk_cache[hostname] = lines
628 begin = 0
629 end = len(lines)
630 hit = None
631 while end - begin > 0:
632 current = (begin + end) / 2
633 parts = lines[current].split(None, 1)
634 comp = parts[0]
635 hit = compare_oids(oid_prefix, comp)
636 if hit == 0:
637 break
638 elif hit == 1: # we are too low
639 begin = current + 1
640 else:
641 end = current
643 if hit != 0:
644 return [] # not found
646 def collect_until(index, direction):
647 rows = []
648 # Handle case, where we run after the end of the lines list
649 if index >= len(lines):
650 if direction > 0:
651 return []
652 else:
653 index -= 1
654 while True:
655 line = lines[index]
656 parts = line.split(None, 1)
657 o = parts[0]
658 if o.startswith('.'):
659 o = o[1:]
660 if o == oid or o.startswith(oid_prefix + "."):
661 if len(parts) > 1:
662 try:
663 value = cmk_base.agent_simulator.process(parts[1])
664 except:
665 value = parts[1] # agent simulator missing in precompiled mode
666 else:
667 value = ""
668 # Fix for missing starting oids
669 rows.append(('.' + o, classic_snmp.strip_snmp_value(value)))
670 index += direction
671 if index < 0 or index >= len(lines):
672 break
673 else:
674 break
675 return rows
677 rowinfo = collect_until(current, -1)
678 rowinfo.reverse()
679 rowinfo += collect_until(current + 1, 1)
681 if dot_star:
682 return [rowinfo[0]]
684 return rowinfo
688 # .--Main modes----------------------------------------------------------.
689 # | __ __ _ _ |
690 # | | \/ | __ _(_)_ __ _ __ ___ ___ __| | ___ ___ |
691 # | | |\/| |/ _` | | '_ \ | '_ ` _ \ / _ \ / _` |/ _ \/ __| |
692 # | | | | | (_| | | | | | | | | | | | (_) | (_| | __/\__ \ |
693 # | |_| |_|\__,_|_|_| |_| |_| |_| |_|\___/ \__,_|\___||___/ |
694 # | |
695 # +----------------------------------------------------------------------+
696 # | Some main modes to help the user |
697 # '----------------------------------------------------------------------'
700 def do_snmptranslate(walk_filename):
701 if not walk_filename:
702 raise MKGeneralException("Please provide the name of a SNMP walk file")
704 walk_path = "%s/%s" % (cmk.utils.paths.snmpwalks_dir, walk_filename)
705 if not os.path.exists(walk_path):
706 raise MKGeneralException("The walk '%s' does not exist" % walk_path)
708 def translate(lines):
709 result_lines = []
710 try:
711 oids_for_command = []
712 for line in lines:
713 oids_for_command.append(line.split(" ")[0])
715 command = ["snmptranslate", "-m", "ALL",
716 "-M+%s" % cmk.utils.paths.local_mibs_dir] + oids_for_command
717 p = subprocess.Popen(
718 command, stdout=subprocess.PIPE, stderr=open(os.devnull, "w"), close_fds=True)
719 p.wait()
720 output = p.stdout.read()
721 result = output.split("\n")[0::2]
722 for idx, line in enumerate(result):
723 result_lines.append((line.strip(), lines[idx].strip()))
725 except Exception as e:
726 console.error("%s\n" % e)
728 return result_lines
730 # Translate n-oid's per cycle
731 entries_per_cycle = 500
732 translated_lines = []
734 walk_lines = file(walk_path).readlines()
735 console.error("Processing %d lines.\n" % len(walk_lines))
737 i = 0
738 while i < len(walk_lines):
739 console.error("\r%d to go... " % (len(walk_lines) - i))
740 process_lines = walk_lines[i:i + entries_per_cycle]
741 translated = translate(process_lines)
742 i += len(translated)
743 translated_lines += translated
744 console.error("\rfinished. \n")
746 # Output formatted
747 for translation, line in translated_lines:
748 console.output("%s --> %s\n" % (line, translation))
751 def do_snmpwalk(options, hostnames):
752 if "oids" in options and "extraoids" in options:
753 raise MKGeneralException("You cannot specify --oid and --extraoid at the same time.")
755 if not hostnames:
756 raise MKBailOut("Please specify host names to walk on.")
758 if not os.path.exists(cmk.utils.paths.snmpwalks_dir):
759 os.makedirs(cmk.utils.paths.snmpwalks_dir)
761 for hostname in hostnames:
762 #TODO: What about SNMP management boards?
763 access_data = {
764 "hostname": hostname,
765 "ipaddress": ip_lookup.lookup_ipv4_address(hostname),
766 "credentials": config.snmp_credentials_of(hostname),
768 try:
769 do_snmpwalk_on(options, access_data, cmk.utils.paths.snmpwalks_dir + "/" + hostname)
770 except Exception as e:
771 console.error("Error walking %s: %s\n" % (hostname, e))
772 if cmk.utils.debug.enabled():
773 raise
774 cmk_base.cleanup.cleanup_globals()
777 def do_snmpwalk_on(options, access_data, filename):
778 hostname = access_data["hostname"]
779 console.verbose("%s:\n" % hostname)
781 oids = oids_to_walk(options)
783 with open(filename, "w") as out:
784 for rows in _execute_walks_for_dump(hostname, access_data, oids):
785 for oid, value in rows:
786 out.write("%s %s\n" % (oid, value))
787 console.verbose("%d variables.\n" % len(rows))
789 console.verbose("Wrote fetched data to %s%s%s.\n" % (tty.bold, filename, tty.normal))
792 def _execute_walks_for_dump(hostname, access_data, oids):
793 for oid in oids:
794 try:
795 console.verbose("Walk on \"%s\"..." % oid)
796 yield walk_for_export(access_data, oid)
797 except Exception as e:
798 console.error("Error: %s\n" % e)
799 if cmk.utils.debug.enabled():
800 raise
803 def oids_to_walk(options=None):
804 if options is None:
805 options = {}
807 oids = [
808 ".1.3.6.1.2.1", # SNMPv2-SMI::mib-2
809 ".1.3.6.1.4.1" # SNMPv2-SMI::enterprises
812 if "oids" in options:
813 oids = options["oids"]
815 elif "extraoids" in options:
816 oids += options["extraoids"]
818 return sorted(oids, key=lambda x: map(int, x.strip(".").split(".")))
821 def do_snmpget(*args):
822 if not args[0]:
823 raise MKBailOut("You need to specify an OID.")
824 oid = args[0][0]
826 hostnames = args[0][1:]
827 if not hostnames:
828 hostnames = []
829 for host in config.all_active_realhosts():
830 if config.is_snmp_host(host):
831 hostnames.append(host)
833 for hostname in hostnames:
834 #TODO what about SNMP management boards?
835 ipaddress = ip_lookup.lookup_ipv4_address(hostname)
836 access_data = {
837 "hostname": hostname,
838 "ipaddress": ipaddress,
839 "credentials": config.snmp_credentials_of(hostname),
841 value = get_single_oid(access_data, oid)
842 console.output("%s (%s): %r\n" % (hostname, ipaddress, value))
843 cmk_base.cleanup.cleanup_globals()
846 cmk_base.cleanup.register_cleanup(cleanup_host_caches)