Licenses: Updated the list of licenses and added a PDF containing all license texts
[check_mk.git] / cmk_base / snmp.py
blob8fda89c7ab0d4278784f8d46976ea3373f9b2c7e
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 # TODO: Move to StoredWalkSNMPBackend?
58 _g_walk_cache = {} # type: Dict[str, List[str]]
61 # .--caching-------------------------------------------------------------.
62 # | _ _ |
63 # | ___ __ _ ___| |__ (_)_ __ __ _ |
64 # | / __/ _` |/ __| '_ \| | '_ \ / _` | |
65 # | | (_| (_| | (__| | | | | | | | (_| | |
66 # | \___\__,_|\___|_| |_|_|_| |_|\__, | |
67 # | |___/ |
68 # '----------------------------------------------------------------------'
70 #TODO CACHING
73 def initialize_single_oid_cache(host_config, from_disk=False):
74 global _g_single_oid_cache, _g_single_oid_ipaddress, _g_single_oid_hostname
76 if not (_g_single_oid_hostname == host_config.hostname \
77 and _g_single_oid_ipaddress == host_config.ipaddress) \
78 or _g_single_oid_cache is None:
79 _g_single_oid_hostname = host_config.hostname
80 _g_single_oid_ipaddress = host_config.ipaddress
81 if from_disk:
82 _g_single_oid_cache = _load_single_oid_cache(host_config)
83 else:
84 _g_single_oid_cache = {}
87 def write_single_oid_cache(host_config):
88 if not _g_single_oid_cache:
89 return
91 cache_dir = cmk.utils.paths.snmp_scan_cache_dir
92 if not os.path.exists(cache_dir):
93 os.makedirs(cache_dir)
94 cache_path = "%s/%s.%s" % (cache_dir, host_config.hostname, host_config.ipaddress)
95 store.save_data_to_file(cache_path, _g_single_oid_cache, pretty=False)
98 def set_single_oid_cache(host_config, oid, value):
99 _g_single_oid_cache[oid] = value
102 def _is_in_single_oid_cache(host_config, oid):
103 return oid in _g_single_oid_cache
106 def _get_oid_from_single_oid_cache(host_config, oid):
107 return _g_single_oid_cache.get(oid)
110 def _load_single_oid_cache(host_config):
111 cache_path = "%s/%s.%s" % (cmk.utils.paths.snmp_scan_cache_dir, host_config.hostname,
112 host_config.ipaddress)
113 return store.load_data_from_file(cache_path, {})
116 def cleanup_host_caches():
117 global _g_walk_cache
118 _g_walk_cache = {}
119 _clear_other_hosts_oid_cache(None)
120 if inline_snmp:
121 inline_snmp.cleanup_inline_snmp_globals()
124 def _clear_other_hosts_oid_cache(hostname):
125 global _g_single_oid_cache, _g_single_oid_ipaddress, _g_single_oid_hostname
126 if _g_single_oid_hostname != hostname:
127 _g_single_oid_cache = None
128 _g_single_oid_hostname = hostname
129 _g_single_oid_ipaddress = None
133 # .--Generic SNMP--------------------------------------------------------.
134 # | ____ _ ____ _ _ __ __ ____ |
135 # | / ___| ___ _ __ ___ _ __(_) ___ / ___|| \ | | \/ | _ \ |
136 # | | | _ / _ \ '_ \ / _ \ '__| |/ __| \___ \| \| | |\/| | |_) | |
137 # | | |_| | __/ | | | __/ | | | (__ ___) | |\ | | | | __/ |
138 # | \____|\___|_| |_|\___|_| |_|\___| |____/|_| \_|_| |_|_| |
139 # | |
140 # +----------------------------------------------------------------------+
141 # | Top level functions to realize SNMP functionality for Check_MK. |
142 # '----------------------------------------------------------------------'
145 def create_snmp_host_config(hostname):
146 # type: (str) -> cmk_base.snmp_utils.SNMPHostConfig
147 return cmk_base.snmp_utils.SNMPHostConfig(
148 is_ipv6_primary=config.is_ipv6_primary(hostname),
149 hostname=hostname,
150 ipaddress=ip_lookup.lookup_ipv4_address(hostname),
151 credentials=config.snmp_credentials_of(hostname),
152 port=config.snmp_port_of(hostname),
153 is_bulkwalk_host=config.is_bulkwalk_host(hostname),
154 is_snmpv2or3_without_bulkwalk_host=config.is_snmpv2or3_without_bulkwalk_host(hostname),
155 bulk_walk_size_of=config.bulk_walk_size_of(hostname),
156 timing=config.snmp_timing_of(hostname),
157 oid_range_limits=config.oid_range_limits_of(hostname),
161 # TODO: OID_END_OCTET_STRING is not used at all. Drop it.
162 def get_snmp_table(host_config, check_plugin_name, oid_info, use_snmpwalk_cache):
163 # oid_info is either ( oid, columns ) or
164 # ( oid, suboids, columns )
165 # suboids is a list if OID-infixes that are put between baseoid
166 # and the columns and also prefixed to the index column. This
167 # allows to merge distinct SNMP subtrees with a similar structure
168 # to one virtual new tree (look into cmctc_temp for an example)
169 if len(oid_info) == 2:
170 oid, targetcolumns = oid_info
171 suboids = [None]
172 else:
173 oid, suboids, targetcolumns = oid_info
175 if not oid.startswith("."):
176 raise MKGeneralException("OID definition '%s' does not begin with ." % oid)
178 index_column = -1
179 index_format = None
180 info = []
181 for suboid in suboids:
182 columns = []
183 # Detect missing (empty columns)
184 max_len = 0
185 max_len_col = -1
187 for colno, column in enumerate(targetcolumns):
188 fetchoid, value_encoding = _compute_fetch_oid(oid, suboid, column)
190 # column may be integer or string like "1.5.4.2.3"
191 # if column is 0, we do not fetch any data from snmp, but use
192 # a running counter as index. If the index column is the first one,
193 # we do not know the number of entries right now. We need to fill
194 # in later. If the column is OID_STRING or OID_BIN we do something
195 # similar: we fill in the complete OID of the entry, either as
196 # string or as binary UTF-8 encoded number string
197 if column in [
198 cmk_base.snmp_utils.OID_END, cmk_base.snmp_utils.OID_STRING,
199 cmk_base.snmp_utils.OID_BIN, cmk_base.snmp_utils.OID_END_BIN,
200 cmk_base.snmp_utils.OID_END_OCTET_STRING
202 if index_column >= 0 and index_column != colno:
203 raise MKGeneralException(
204 "Invalid SNMP OID specification in implementation of check. "
205 "You can only use one of OID_END, OID_STRING, OID_BIN, OID_END_BIN and OID_END_OCTET_STRING."
207 index_column = colno
208 columns.append((fetchoid, [], "string"))
209 index_format = column
210 continue
212 rowinfo = _get_snmpwalk(host_config, check_plugin_name, oid, fetchoid, column,
213 use_snmpwalk_cache)
215 columns.append((fetchoid, rowinfo, value_encoding))
216 number_of_rows = len(rowinfo)
217 if number_of_rows > max_len:
218 max_len = number_of_rows
219 max_len_col = colno
221 if index_column != -1:
222 index_rows = []
223 # Take end-oids of non-index columns as indices
224 fetchoid, max_column, value_encoding = columns[max_len_col]
225 for o, _unused_value in max_column:
226 if index_format == cmk_base.snmp_utils.OID_END:
227 index_rows.append((o, _extract_end_oid(fetchoid, o)))
228 elif index_format == cmk_base.snmp_utils.OID_STRING:
229 index_rows.append((o, o))
230 elif index_format == cmk_base.snmp_utils.OID_BIN:
231 index_rows.append((o, _oid_to_bin(o)))
232 elif index_format == cmk_base.snmp_utils.OID_END_BIN:
233 index_rows.append((o, _oid_to_bin(_extract_end_oid(fetchoid, o))))
234 elif index_format == cmk_base.snmp_utils.OID_END_OCTET_STRING:
235 index_rows.append((o, _oid_to_bin(_extract_end_oid(fetchoid, o))[1:]))
236 else:
237 raise MKGeneralException("Invalid index format %s" % index_format)
239 index_encoding = columns[index_column][-1]
240 columns[index_column] = fetchoid, index_rows, index_encoding
242 # prepend suboid to first column
243 if suboid and columns:
244 fetchoid, first_column, value_encoding = columns[0]
245 new_first_column = []
246 for o, val in first_column:
247 new_first_column.append((o, str(suboid) + "." + str(val)))
248 columns[0] = fetchoid, new_first_column, value_encoding
250 # Here we have to deal with a nasty problem: Some brain-dead devices
251 # omit entries in some sub OIDs. This happens e.g. for CISCO 3650
252 # in the interfaces MIB with 64 bit counters. So we need to look at
253 # the OIDs and watch out for gaps we need to fill with dummy values.
254 new_columns = _sanitize_snmp_table_columns(columns)
256 # From all SNMP data sources (stored walk, classic SNMP, inline SNMP) we
257 # get normal python strings. But for Check_MK we need unicode strings now.
258 # Convert them by using the standard Check_MK approach for incoming data
259 sanitized_columns = _sanitize_snmp_encoding(host_config.hostname, new_columns)
261 info += _construct_snmp_table_of_rows(sanitized_columns)
263 return info
266 # Contextes can only be used when check_plugin_name is given.
267 def get_single_oid(host_config, oid, check_plugin_name=None, do_snmp_scan=True):
268 # The OID can end with ".*". In that case we do a snmpgetnext and try to
269 # find an OID with the prefix in question. The *cache* is working including
270 # the X, however.
271 if oid[0] != '.':
272 if cmk.utils.debug.enabled():
273 raise MKGeneralException("OID definition '%s' does not begin with a '.'" % oid)
274 else:
275 oid = '.' + oid
277 # TODO: Use generic cache mechanism
278 if _is_in_single_oid_cache(host_config, oid):
279 console.vverbose(" Using cached OID %s: " % oid)
280 value = _get_oid_from_single_oid_cache(host_config, oid)
281 console.vverbose("%s%s%s%s\n" % (tty.bold, tty.green, value, tty.normal))
282 return value
284 # get_single_oid() can only return a single value. When SNMPv3 is used with multiple
285 # SNMP contexts, all contextes will be queried until the first answer is received.
286 if check_plugin_name is not None and cmk_base.snmp_utils.is_snmpv3_host(host_config):
287 snmp_contexts = _snmpv3_contexts_of(host_config.hostname, check_plugin_name)
288 else:
289 snmp_contexts = [None]
291 console.vverbose(" Getting OID %s: " % oid)
292 for context_name in snmp_contexts:
293 try:
294 snmp_backend = SNMPBackendFactory().factory(
295 host_config, enforce_stored_walks=_enforce_stored_walks)
296 value = snmp_backend.get(host_config, oid, context_name)
298 if value is not None:
299 break # Use first received answer in case of multiple contextes
300 except:
301 if cmk.utils.debug.enabled():
302 raise
303 value = None
305 if value is not None:
306 console.vverbose("%s%s%s%s\n" % (tty.bold, tty.green, value, tty.normal))
307 else:
308 console.vverbose("failed.\n")
310 set_single_oid_cache(host_config, oid, value)
311 return value
314 class SNMPBackendFactory(object):
315 @staticmethod
316 def factory(host_config, enforce_stored_walks):
317 if enforce_stored_walks or config.is_usewalk_host(host_config.hostname):
318 return StoredWalkSNMPBackend()
320 if config.is_inline_snmp_host(host_config.hostname):
321 return inline_snmp.InlineSNMPBackend()
323 return classic_snmp.ClassicSNMPBackend()
326 class StoredWalkSNMPBackend(cmk_base.snmp_utils.ABCSNMPBackend):
327 def get(self, host_config, oid, context_name=None):
328 walk = self.walk(host_config, oid)
330 # get_stored_snmpwalk returns all oids that start with oid but here
331 # we need an exact match
332 if len(walk) == 1 and oid == walk[0][0]:
333 return walk[0][1]
335 elif oid.endswith(".*") and len(walk) > 0:
336 return walk[0][1]
338 return None
340 def walk(self, host_config, oid):
341 if oid.startswith("."):
342 oid = oid[1:]
344 if oid.endswith(".*"):
345 oid_prefix = oid[:-2]
346 dot_star = True
347 else:
348 oid_prefix = oid
349 dot_star = False
351 path = cmk.utils.paths.snmpwalks_dir + "/" + host_config.hostname
353 console.vverbose(" Loading %s from %s\n" % (oid, path))
355 rowinfo = []
357 if host_config.hostname in _g_walk_cache:
358 lines = _g_walk_cache[host_config.hostname]
359 else:
360 try:
361 lines = file(path).readlines()
362 except IOError:
363 raise MKSNMPError("No snmpwalk file %s" % path)
364 _g_walk_cache[host_config.hostname] = lines
366 begin = 0
367 end = len(lines)
368 hit = None
369 while end - begin > 0:
370 current = (begin + end) / 2
371 parts = lines[current].split(None, 1)
372 comp = parts[0]
373 hit = self._compare_oids(oid_prefix, comp)
374 if hit == 0:
375 break
376 elif hit == 1: # we are too low
377 begin = current + 1
378 else:
379 end = current
381 if hit != 0:
382 return [] # not found
384 rowinfo = self._collect_until(oid, oid_prefix, lines, current, -1)
385 rowinfo.reverse()
386 rowinfo += self._collect_until(oid, oid_prefix, lines, current + 1, 1)
388 if dot_star:
389 return [rowinfo[0]]
391 return rowinfo
393 def _compare_oids(self, a, b):
394 aa = self._to_bin_string(a)
395 bb = self._to_bin_string(b)
396 if len(aa) <= len(bb) and bb[:len(aa)] == aa:
397 result = 0
398 else:
399 result = cmp(aa, bb)
400 return result
402 def _to_bin_string(self, oid):
403 try:
404 return tuple(map(int, oid.strip(".").split(".")))
405 except:
406 raise MKGeneralException("Invalid OID %s" % oid)
408 def _collect_until(self, oid, oid_prefix, lines, index, direction):
409 rows = []
410 # Handle case, where we run after the end of the lines list
411 if index >= len(lines):
412 if direction > 0:
413 return []
414 else:
415 index -= 1
416 while True:
417 line = lines[index]
418 parts = line.split(None, 1)
419 o = parts[0]
420 if o.startswith('.'):
421 o = o[1:]
422 if o == oid or o.startswith(oid_prefix + "."):
423 if len(parts) > 1:
424 try:
425 value = cmk_base.agent_simulator.process(parts[1])
426 except:
427 value = parts[1] # agent simulator missing in precompiled mode
428 else:
429 value = ""
430 # Fix for missing starting oids
431 rows.append(('.' + o, classic_snmp.strip_snmp_value(value)))
432 index += direction
433 if index < 0 or index >= len(lines):
434 break
435 else:
436 break
437 return rows
440 def walk_for_export(host_config, oid):
441 if config.is_inline_snmp_host(host_config.hostname):
442 rows = inline_snmp.walk(host_config, None, oid)
443 return inline_snmp.convert_rows_for_stored_walk(rows)
445 return classic_snmp.walk(host_config, oid, hex_plain=True)
448 def enforce_use_stored_walks():
449 global _enforce_stored_walks
450 _enforce_stored_walks = True
454 # .--SNMP helpers--------------------------------------------------------.
455 # | ____ _ _ __ __ ____ _ _ |
456 # | / ___|| \ | | \/ | _ \ | |__ ___| |_ __ ___ _ __ ___ |
457 # | \___ \| \| | |\/| | |_) | | '_ \ / _ \ | '_ \ / _ \ '__/ __| |
458 # | ___) | |\ | | | | __/ | | | | __/ | |_) | __/ | \__ \ |
459 # | |____/|_| \_|_| |_|_| |_| |_|\___|_| .__/ \___|_| |___/ |
460 # | |_| |
461 # +----------------------------------------------------------------------+
462 # | Internal helpers for processing SNMP things |
463 # '----------------------------------------------------------------------'
466 def _oid_to_bin(oid):
467 return u"".join([unichr(int(p)) for p in oid.strip(".").split(".")])
470 def _extract_end_oid(prefix, complete):
471 return complete[len(prefix):].lstrip('.')
474 # sort OID strings numerically
475 def _oid_to_intlist(oid):
476 if oid:
477 return map(int, oid.split('.'))
479 return []
482 def _cmp_oids(o1, o2):
483 return cmp(_oid_to_intlist(o1), _oid_to_intlist(o2))
486 def _cmp_oid_pairs(pair1, pair2):
487 return cmp(_oid_to_intlist(pair1[0].lstrip('.')), _oid_to_intlist(pair2[0].lstrip('.')))
490 def _snmpv3_contexts_of(hostname, check_plugin_name):
491 for ty, rules in config.snmpv3_contexts_of(hostname):
492 if ty is None or ty == check_plugin_name:
493 return rules
494 return [None]
497 def _get_snmpwalk(host_config, check_plugin_name, oid, fetchoid, column, use_snmpwalk_cache):
498 is_cachable = _is_snmpwalk_cachable(column)
499 rowinfo = None
500 if is_cachable and use_snmpwalk_cache:
501 # Returns either the cached SNMP walk or None when nothing is cached
502 rowinfo = _get_cached_snmpwalk(host_config.hostname, fetchoid)
504 if rowinfo is None:
505 rowinfo = _perform_snmpwalk(host_config, check_plugin_name, oid, fetchoid)
507 if is_cachable:
508 _save_snmpwalk_cache(host_config.hostname, fetchoid, rowinfo)
510 return rowinfo
513 def _perform_snmpwalk(host_config, check_plugin_name, base_oid, fetchoid):
514 added_oids = set([])
515 rowinfo = []
516 if cmk_base.snmp_utils.is_snmpv3_host(host_config):
517 snmp_contexts = _snmpv3_contexts_of(host_config.hostname, check_plugin_name)
518 else:
519 snmp_contexts = [None]
521 for context_name in snmp_contexts:
522 if _enforce_stored_walks or config.is_usewalk_host(host_config.hostname):
523 rows = StoredWalkSNMPBackend().walk(host_config, fetchoid)
525 elif config.is_inline_snmp_host(host_config.hostname):
526 rows = inline_snmp.walk(
527 host_config, check_plugin_name, fetchoid, base_oid, context_name=context_name)
528 else:
529 rows = classic_snmp.walk(host_config, fetchoid, context_name=context_name)
531 # I've seen a broken device (Mikrotik Router), that broke after an
532 # update to RouterOS v6.22. It would return 9 time the same OID when
533 # .1.3.6.1.2.1.1.1.0 was being walked. We try to detect these situations
534 # by removing any duplicate OID information
535 if len(rows) > 1 and rows[0][0] == rows[1][0]:
536 console.vverbose(
537 "Detected broken SNMP agent. Ignoring duplicate OID %s.\n" % rows[0][0])
538 rows = rows[:1]
540 for row_oid, val in rows:
541 if row_oid in added_oids:
542 console.vverbose("Duplicate OID found: %s (%s)\n" % (row_oid, val))
543 else:
544 rowinfo.append((row_oid, val))
545 added_oids.add(row_oid)
547 return rowinfo
550 def _compute_fetch_oid(oid, suboid, column):
551 fetchoid = oid
552 value_encoding = "string"
554 if suboid:
555 fetchoid += "." + str(suboid)
557 if column != "":
558 if isinstance(column, tuple):
559 fetchoid += "." + str(column[1])
560 if column[0] == "binary":
561 value_encoding = "binary"
562 else:
563 fetchoid += "." + str(column)
565 return fetchoid, value_encoding
568 def _sanitize_snmp_encoding(hostname, columns):
569 decode_string_func = lambda s: _snmp_decode_string(hostname, s)
571 for index, (column, value_encoding) in enumerate(columns):
572 if value_encoding == "string":
573 columns[index] = map(decode_string_func, column)
574 else:
575 columns[index] = map(_snmp_decode_binary, column)
576 return columns
579 def _snmp_decode_string(hostname, text):
580 encoding = config.snmp_character_encoding_of(hostname)
581 if encoding:
582 return text.decode(encoding)
583 else:
584 # Try to determine the current string encoding. In case a UTF-8 decoding fails, we decode latin1.
585 try:
586 return text.decode('utf-8')
587 except:
588 return text.decode('latin1')
591 def _snmp_decode_binary(text):
592 return map(ord, text)
595 def _sanitize_snmp_table_columns(columns):
596 # First compute the complete list of end-oids appearing in the output
597 # by looping all results and putting the endoids to a flat list
598 endoids = []
599 for fetchoid, column, value_encoding in columns:
600 for o, value in column:
601 endoid = _extract_end_oid(fetchoid, o)
602 if endoid not in endoids:
603 endoids.append(endoid)
605 # The list needs to be sorted to prevent problems when the first
606 # column has missing values in the middle of the tree.
607 if not _are_ascending_oids(endoids):
608 endoids.sort(cmp=_cmp_oids)
609 need_sort = True
610 else:
611 need_sort = False
613 # Now fill gaps in columns where some endois are missing
614 new_columns = []
615 for fetchoid, column, value_encoding in columns:
616 # It might happen that end OIDs are not ordered. Fix the OID sorting to make
617 # it comparable to the already sorted endoids list. Otherwise we would get
618 # some mixups when filling gaps
619 if need_sort:
620 column.sort(cmp=_cmp_oid_pairs)
622 i = 0
623 new_column = []
624 # Loop all lines to fill holes in the middle of the list. All
625 # columns check the following lines for the correct endoid. If
626 # an endoid differs empty values are added until the hole is filled
627 for o, value in column:
628 eo = _extract_end_oid(fetchoid, o)
629 if len(column) != len(endoids):
630 while i < len(endoids) and endoids[i] != eo:
631 new_column.append("") # (beginoid + '.' +endoids[i], "" ) )
632 i += 1
633 new_column.append(value)
634 i += 1
636 # At the end check if trailing OIDs are missing
637 while i < len(endoids):
638 new_column.append("") # (beginoid + '.' +endoids[i], "") )
639 i += 1
640 new_columns.append((new_column, value_encoding))
642 return new_columns
645 def _are_ascending_oids(oid_list):
646 for a in range(len(oid_list) - 1):
647 if _cmp_oids(oid_list[a], oid_list[a + 1]) > 0: # == 0 should never happen
648 return False
649 return True
652 def _construct_snmp_table_of_rows(columns):
653 if not columns:
654 return []
656 # Now construct table by swapping X and Y.
657 new_info = []
658 for index in range(len(columns[0])):
659 row = [c[index] for c in columns]
660 new_info.append(row)
661 return new_info
664 def _is_snmpwalk_cachable(column):
665 return isinstance(column, tuple) and column[0] == "cached"
668 def _get_cached_snmpwalk(hostname, fetchoid):
669 path = _snmpwalk_cache_path(hostname, fetchoid)
670 try:
671 console.vverbose(" Loading %s from walk cache %s\n" % (fetchoid, path))
672 return store.load_data_from_file(path)
673 except:
674 if cmk.utils.debug.enabled():
675 raise
676 console.verbose(" Failed loading walk cache. Continue without it.\n" % path)
677 return None
680 def _save_snmpwalk_cache(hostname, fetchoid, rowinfo):
681 path = _snmpwalk_cache_path(hostname, fetchoid)
683 if not os.path.exists(os.path.dirname(path)):
684 os.makedirs(os.path.dirname(path))
686 console.vverbose(" Saving walk of %s to walk cache %s\n" % (fetchoid, path))
687 store.save_data_to_file(path, rowinfo, pretty=False)
690 def _snmpwalk_cache_path(hostname, fetchoid):
691 return os.path.join(cmk.utils.paths.var_dir, "snmp_cache", hostname, fetchoid)
695 # .--Main modes----------------------------------------------------------.
696 # | __ __ _ _ |
697 # | | \/ | __ _(_)_ __ _ __ ___ ___ __| | ___ ___ |
698 # | | |\/| |/ _` | | '_ \ | '_ ` _ \ / _ \ / _` |/ _ \/ __| |
699 # | | | | | (_| | | | | | | | | | | | (_) | (_| | __/\__ \ |
700 # | |_| |_|\__,_|_|_| |_| |_| |_| |_|\___/ \__,_|\___||___/ |
701 # | |
702 # +----------------------------------------------------------------------+
703 # | Some main modes to help the user |
704 # '----------------------------------------------------------------------'
707 def do_snmptranslate(walk_filename):
708 if not walk_filename:
709 raise MKGeneralException("Please provide the name of a SNMP walk file")
711 walk_path = "%s/%s" % (cmk.utils.paths.snmpwalks_dir, walk_filename)
712 if not os.path.exists(walk_path):
713 raise MKGeneralException("The walk '%s' does not exist" % walk_path)
715 def translate(lines):
716 result_lines = []
717 try:
718 oids_for_command = []
719 for line in lines:
720 oids_for_command.append(line.split(" ")[0])
722 command = ["snmptranslate", "-m", "ALL",
723 "-M+%s" % cmk.utils.paths.local_mibs_dir] + oids_for_command
724 p = subprocess.Popen(
725 command, stdout=subprocess.PIPE, stderr=open(os.devnull, "w"), close_fds=True)
726 p.wait()
727 output = p.stdout.read()
728 result = output.split("\n")[0::2]
729 for idx, line in enumerate(result):
730 result_lines.append((line.strip(), lines[idx].strip()))
732 except Exception as e:
733 console.error("%s\n" % e)
735 return result_lines
737 # Translate n-oid's per cycle
738 entries_per_cycle = 500
739 translated_lines = []
741 walk_lines = file(walk_path).readlines()
742 console.error("Processing %d lines.\n" % len(walk_lines))
744 i = 0
745 while i < len(walk_lines):
746 console.error("\r%d to go... " % (len(walk_lines) - i))
747 process_lines = walk_lines[i:i + entries_per_cycle]
748 translated = translate(process_lines)
749 i += len(translated)
750 translated_lines += translated
751 console.error("\rfinished. \n")
753 # Output formatted
754 for translation, line in translated_lines:
755 console.output("%s --> %s\n" % (line, translation))
758 def do_snmpwalk(options, hostnames):
759 if "oids" in options and "extraoids" in options:
760 raise MKGeneralException("You cannot specify --oid and --extraoid at the same time.")
762 if not hostnames:
763 raise MKBailOut("Please specify host names to walk on.")
765 if not os.path.exists(cmk.utils.paths.snmpwalks_dir):
766 os.makedirs(cmk.utils.paths.snmpwalks_dir)
768 for hostname in hostnames:
769 #TODO: What about SNMP management boards?
770 host_config = create_snmp_host_config(hostname)
772 try:
773 _do_snmpwalk_on(host_config, options, cmk.utils.paths.snmpwalks_dir + "/" + hostname)
774 except Exception as e:
775 console.error("Error walking %s: %s\n" % (hostname, e))
776 if cmk.utils.debug.enabled():
777 raise
778 cmk_base.cleanup.cleanup_globals()
781 def _do_snmpwalk_on(host_config, options, filename):
782 console.verbose("%s:\n" % host_config.hostname)
784 oids = oids_to_walk(options)
786 with open(filename, "w") as out:
787 for rows in _execute_walks_for_dump(host_config, oids):
788 for oid, value in rows:
789 out.write("%s %s\n" % (oid, value))
790 console.verbose("%d variables.\n" % len(rows))
792 console.verbose("Wrote fetched data to %s%s%s.\n" % (tty.bold, filename, tty.normal))
795 def _execute_walks_for_dump(host_config, oids):
796 for oid in oids:
797 try:
798 console.verbose("Walk on \"%s\"..." % oid)
799 yield walk_for_export(host_config, oid)
800 except Exception as e:
801 console.error("Error: %s\n" % e)
802 if cmk.utils.debug.enabled():
803 raise
806 def oids_to_walk(options=None):
807 if options is None:
808 options = {}
810 oids = [
811 ".1.3.6.1.2.1", # SNMPv2-SMI::mib-2
812 ".1.3.6.1.4.1" # SNMPv2-SMI::enterprises
815 if "oids" in options:
816 oids = options["oids"]
818 elif "extraoids" in options:
819 oids += options["extraoids"]
821 return sorted(oids, key=lambda x: map(int, x.strip(".").split(".")))
824 def do_snmpget(*args):
825 if not args[0]:
826 raise MKBailOut("You need to specify an OID.")
827 oid = args[0][0]
829 config_cache = config.get_config_cache()
831 hostnames = args[0][1:]
832 if not hostnames:
833 hostnames = []
834 for host in config.all_active_realhosts():
835 host_config = config_cache.get_host_config(host)
836 if host_config.is_snmp_host:
837 hostnames.append(host)
839 for hostname in hostnames:
840 #TODO what about SNMP management boards?
841 host_config = create_snmp_host_config(hostname)
843 value = get_single_oid(host_config, oid)
844 console.output("%s (%s): %r\n" % (hostname, host_config.ipaddress, value))
845 cmk_base.cleanup.cleanup_globals()
848 cmk_base.cleanup.register_cleanup(cleanup_host_caches)