Licenses: Updated the list of licenses and added a PDF containing all license texts
[check_mk.git] / cmk_base / events.py
blob09ffec842f6336f23bdc4bb20684098b26999b65
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 # This file is being used by the rule based notifications and (CEE
28 # only) by the alert handling
30 import os
31 import sys
32 import select
33 import socket
34 import time
35 import traceback
36 import urllib
38 import six
40 # suppress "Cannot find module" error from mypy
41 import livestatus # type: ignore
42 import cmk
43 from cmk.utils.regex import regex
44 import cmk.utils.debug
45 import cmk.utils.daemon
47 import cmk_base.config as config
48 import cmk_base.core
51 def event_keepalive(event_function,
52 log_function,
53 call_every_loop=None,
54 loop_interval=None,
55 shutdown_function=None):
56 last_config_timestamp = config_timestamp()
58 # Send signal that we are ready to receive the next event, but
59 # not after a config-reload-restart (see below)
60 if os.getenv("CMK_EVENT_RESTART") != "1":
61 log_function("Starting in keepalive mode with PID %d" % os.getpid())
62 sys.stdout.write("*")
63 sys.stdout.flush()
64 else:
65 log_function("We are back after a restart.")
67 while True:
68 try:
69 # Invalidate timeperiod caches
70 cmk_base.core.cleanup_timeperiod_caches()
72 # If the configuration has changed, we do a restart. But we do
73 # this check just before the next event arrives. We must
74 # *not* read data from stdin, just peek! There is still one
75 # problem: when restarting we must *not* send the initial '*'
76 # byte, because that must be not no sooner then the events
77 # has been sent. We do this by setting the environment variable
78 # CMK_EVENT_RESTART=1
80 if event_data_available(loop_interval):
81 if last_config_timestamp != config_timestamp():
82 log_function("Configuration has changed. Restarting myself.")
83 if shutdown_function:
84 shutdown_function()
86 os.putenv("CMK_EVENT_RESTART", "1")
88 # Close all unexpected file descriptors before invoking
89 # execvp() to prevent inheritance of them. In CMK-1085 we
90 # had an issue related to os.urandom() which kept FDs open.
91 # This specific issue of Python 2.7.9 should've been fixed
92 # since Python 2.7.10. Just to be sure we keep cleaning up.
93 cmk.utils.daemon.closefrom(3)
95 os.execvp("cmk", sys.argv)
97 data = ""
98 while not data.endswith("\n\n"):
99 try:
100 new_data = ""
101 new_data = os.read(0, 32768)
102 except IOError as e:
103 new_data = ""
104 except Exception as e:
105 if cmk.utils.debug.enabled():
106 raise
107 log_function("Cannot read data from CMC: %s" % e)
109 if not new_data:
110 log_function("CMC has closed the connection. Shutting down.")
111 if shutdown_function:
112 shutdown_function()
113 sys.exit(0) # closed stdin, this is
114 data += new_data
116 try:
117 context = raw_context_from_string(data.rstrip('\n'))
118 event_function(context)
119 except Exception as e:
120 if cmk.utils.debug.enabled():
121 raise
122 log_function("ERROR %s\n%s" % (e, traceback.format_exc()))
124 # Signal that we are ready for the next event
125 sys.stdout.write("*")
126 sys.stdout.flush()
128 # Fix vor Python 2.4:
129 except SystemExit as e:
130 sys.exit(e)
131 except Exception as e:
132 if cmk.utils.debug.enabled():
133 raise
134 log_function("ERROR %s\n%s" % (e, traceback.format_exc()))
136 if call_every_loop:
137 try:
138 call_every_loop()
139 except Exception as e:
140 if cmk.utils.debug.enabled():
141 raise
142 log_function("ERROR %s\n%s" % (e, traceback.format_exc()))
145 def config_timestamp():
146 mtime = 0
147 for dirpath, _unused_dirnames, filenames in os.walk(cmk.utils.paths.check_mk_config_dir):
148 for f in filenames:
149 mtime = max(mtime, os.stat(dirpath + "/" + f).st_mtime)
151 for path in [
152 cmk.utils.paths.main_config_file, cmk.utils.paths.final_config_file,
153 cmk.utils.paths.local_config_file
155 try:
156 mtime = max(mtime, os.stat(path).st_mtime)
157 except:
158 pass
159 return mtime
162 def event_data_available(loop_interval):
163 return bool(select.select([0], [], [], loop_interval)[0])
166 def raw_context_from_string(data):
167 # Context is line-by-line in g_notify_readahead_buffer
168 context = {}
169 try:
170 for line in data.split('\n'):
171 varname, value = line.strip().split("=", 1)
172 context[varname] = expand_backslashes(value)
173 except Exception: # line without '=' ignored or alerted
174 if cmk.utils.debug.enabled():
175 raise
176 return context
179 def raw_context_from_stdin():
180 context = {}
181 for line in sys.stdin:
182 varname, value = line.strip().split("=", 1)
183 context[varname] = expand_backslashes(value)
184 return context
187 def expand_backslashes(value):
188 # We cannot do the following:
189 # value.replace(r"\n", "\n").replace("\\\\", "\\")
190 # \\n would be exapnded to \<LF> instead of \n. This was a bug
191 # in previous versions.
192 return value.replace("\\\\", "\0").replace("\\n", "\n").replace("\0", "\\")
195 def convert_context_to_unicode(context):
196 # Convert all values to unicode
197 for key, value in context.iteritems():
198 if isinstance(value, str):
199 try:
200 value_unicode = value.decode("utf-8")
201 except:
202 try:
203 value_unicode = value.decode("latin-1")
204 except:
205 value_unicode = u"(Invalid byte sequence)"
206 context[key] = value_unicode
209 def render_context_dump(raw_context):
210 encoded_context = dict(raw_context.items())
211 convert_context_to_unicode(encoded_context)
212 return "Raw context:\n" \
213 + "\n".join([" %s=%s" % v for v in sorted(encoded_context.items())])
216 def event_log(logfile_path, message):
217 if isinstance(message, str):
218 message = message.decode("utf-8")
219 formatted = u"%s %s\n" % (time.strftime("%F %T", time.localtime()), message)
220 file(logfile_path, "a").write(formatted.encode("utf-8"))
223 def find_host_service_in_context(context):
224 host = context.get("HOSTNAME", "UNKNOWN")
225 service = context.get("SERVICEDESC")
226 if service:
227 return host + ";" + service
228 return host
231 # Fetch information about an objects contacts via Livestatus. This is
232 # neccessary for notifications from Nagios, which does not send this
233 # information in macros.
234 def livestatus_fetch_contacts(host, service):
235 try:
236 if service:
237 query = "GET services\nFilter: host_name = %s\nFilter: service_description = %s\nColumns: contacts" % (
238 host, service)
239 else:
240 query = "GET hosts\nFilter: host_name = %s\nColumns: contacts" % host
242 contact_list = livestatus.LocalConnection().query_value(query)
243 if "check-mk-notify" in contact_list: # Remove artifical contact used for rule based notifications
244 contact_list.remove("check-mk-notify")
245 return contact_list
247 except livestatus.MKLivestatusNotFoundError:
248 if not service:
249 return None
251 # Service not found: try again with contacts of host!
252 return livestatus_fetch_contacts(host, None)
254 except Exception:
255 if cmk.utils.debug.enabled():
256 raise
257 return None # We must allow notifications without Livestatus access
260 def add_rulebased_macros(raw_context):
261 # For the rule based notifications we need the list of contacts
262 # an object has. The CMC does send this in the macro "CONTACTS"
263 if "CONTACTS" not in raw_context:
264 contact_list = livestatus_fetch_contacts(raw_context["HOSTNAME"],
265 raw_context.get("SERVICEDESC"))
266 if contact_list is not None:
267 raw_context["CONTACTS"] = ",".join(contact_list)
268 else:
269 raw_context["CONTACTS"] = "?" # means: contacts could not be determined!
271 # Add a pseudo contact name. This is needed for the correct creation
272 # of spool files. Spool files are created on a per-contact-base, as in classical
273 # notifications the core sends out one individual notification per contact.
274 # In the case of rule based notifications we do not make distinctions between
275 # the various contacts.
276 raw_context["CONTACTNAME"] = "check-mk-notify"
279 def complete_raw_context(raw_context, with_dump, log_func):
280 """Extend the raw notification context
282 This ensures that all raw contexts processed in the notification code has specific variables
283 set. Add a few further helper variables that are useful in notification and alert plugins.
285 Please not that this is not only executed on the source system. When notifications are
286 forwarded to another site and the analysis is executed on that site, this function will be
287 executed on the central site. So be sure not to overwrite site specific things.
289 raw_keys = list(raw_context.keys())
291 try:
292 raw_context["WHAT"] = "SERVICE" if raw_context.get("SERVICEDESC") else "HOST"
294 raw_context.setdefault("MONITORING_HOST", socket.gethostname())
295 raw_context.setdefault("OMD_ROOT", cmk.utils.paths.omd_root)
296 raw_context.setdefault("OMD_SITE", cmk.omd_site())
298 # The Check_MK Micro Core sends the MICROTIME and no other time stamps. We add
299 # a few Nagios-like variants in order to be compatible
300 if "MICROTIME" in raw_context:
301 microtime = int(raw_context["MICROTIME"])
302 timestamp = float(microtime) / 1000000.0
303 broken = time.localtime(timestamp)
304 raw_context["DATE"] = time.strftime("%Y-%m-%d", broken)
305 raw_context["SHORTDATETIME"] = time.strftime("%Y-%m-%d %H:%M:%S", broken)
306 raw_context["LONGDATETIME"] = time.strftime("%a %b %d %H:%M:%S %Z %Y", broken)
307 elif "MICROTIME" not in raw_context:
308 # In case the microtime is not provided, e.g. when using Nagios, then set it here
309 # from the current time. We could look for "LONGDATETIME" and calculate the timestamp
310 # from that one, but we try to keep this simple here.
311 raw_context["MICROTIME"] = "%d" % (time.time() * 1000000)
313 raw_context['HOSTURL'] = '/check_mk/index.py?start_url=%s' % \
314 urllib.quote('view.py?view_name=hoststatus&host=%s&site=%s' % (raw_context['HOSTNAME'], raw_context['OMD_SITE']))
315 if raw_context['WHAT'] == 'SERVICE':
316 raw_context['SERVICEURL'] = '/check_mk/index.py?start_url=%s' % \
317 urllib.quote('view.py?view_name=service&host=%s&service=%s&site=%s' %
318 (raw_context['HOSTNAME'], raw_context['SERVICEDESC'], raw_context['OMD_SITE']))
320 # Relative Timestamps for several macros
321 for macro in [
322 'LASTHOSTSTATECHANGE', 'LASTSERVICESTATECHANGE', 'LASTHOSTUP', 'LASTSERVICEOK'
324 if macro in raw_context:
325 raw_context[macro + '_REL'] = get_readable_rel_date(raw_context[macro])
327 # Rule based notifications enabled? We might need to complete a few macros
328 contact = raw_context.get("CONTACTNAME")
329 if not contact or contact == "check-mk-notify":
330 add_rulebased_macros(raw_context)
332 # For custom notifications the number is set to 0 by the core (Nagios and CMC). We force at least
333 # number 1 here, so that rules with conditions on numbers do not fail (the minimum is 1 here)
334 for what in ["HOST", "SERVICE"]:
335 key = what + "NOTIFICATIONNUMBER"
336 if key in raw_context and raw_context[key] == "0":
337 raw_context[key] = "1"
339 # Add the previous hard state. This is neccessary for notification rules that depend on certain transitions,
340 # like OK -> WARN (but not CRIT -> WARN). The CMC sends PREVIOUSHOSTHARDSTATE and PREVIOUSSERVICEHARDSTATE.
341 # Nagios does not have this information and we try to deduct this.
342 if "PREVIOUSHOSTHARDSTATE" not in raw_context and "LASTHOSTSTATE" in raw_context:
343 prev_state = raw_context["LASTHOSTSTATE"]
344 # When the attempts are > 1 then the last state could be identical with
345 # the current one, e.g. both critical. In that case we assume the
346 # previous hard state to be OK.
347 if prev_state == raw_context["HOSTSTATE"]:
348 prev_state = "UP"
349 elif "HOSTATTEMPT" not in raw_context or \
350 ("HOSTATTEMPT" in raw_context and raw_context["HOSTATTEMPT"] != "1"):
351 # Here We do not know. The transition might be OK -> WARN -> CRIT and
352 # the initial OK is completely lost. We use the artificial state "?"
353 # here, which matches all states and makes sure that when in doubt a
354 # notification is being sent out. But when the new state is UP, then
355 # we know that the previous state was a hard state (otherwise there
356 # would not have been any notification)
357 if raw_context["HOSTSTATE"] != "UP":
358 prev_state = "?"
359 log_func("Previous host hard state not known. Allowing all states.")
360 raw_context["PREVIOUSHOSTHARDSTATE"] = prev_state
362 # Same for services
363 if raw_context["WHAT"] == "SERVICE" and "PREVIOUSSERVICEHARDSTATE" not in raw_context:
364 prev_state = raw_context["LASTSERVICESTATE"]
365 if prev_state == raw_context["SERVICESTATE"]:
366 prev_state = "OK"
367 elif "SERVICEATTEMPT" not in raw_context or \
368 ("SERVICEATTEMPT" in raw_context and raw_context["SERVICEATTEMPT"] != "1"):
369 if raw_context["SERVICESTATE"] != "OK":
370 prev_state = "?"
371 log_func("Previous service hard state not known. Allowing all states.")
372 raw_context["PREVIOUSSERVICEHARDSTATE"] = prev_state
374 # Add short variants for state names (at most 4 characters)
375 for key, value in raw_context.items():
376 if key.endswith("STATE"):
377 raw_context[key[:-5] + "SHORTSTATE"] = value[:4]
379 if raw_context["WHAT"] == "SERVICE":
380 raw_context['SERVICEFORURL'] = urllib.quote(raw_context['SERVICEDESC'])
381 raw_context['HOSTFORURL'] = urllib.quote(raw_context['HOSTNAME'])
383 # Add HTML formated plugin output
384 if "HOSTOUTPUT" in raw_context:
385 raw_context["HOSTOUTPUT_HTML"] = format_plugin_output(raw_context["HOSTOUTPUT"])
386 if raw_context["WHAT"] == "SERVICE":
387 raw_context["SERVICEOUTPUT_HTML"] = format_plugin_output(raw_context["SERVICEOUTPUT"])
388 raw_context["LONGSERVICEOUTPUT_HTML"] = format_plugin_output(
389 raw_context["LONGSERVICEOUTPUT"])
391 convert_context_to_unicode(raw_context)
393 except Exception as e:
394 log_func("Error on completing raw context: %s" % e)
396 if with_dump:
397 log_func("Computed variables:\n" + "\n".join(
398 sorted([
399 " %s=%s" % (k, raw_context[k])
400 for k in raw_context
401 if k not in raw_keys
402 ])))
405 # There is common code with web/htdocs/lib.py:format_plugin_output(). Please check
406 # whether or not that function needs to be changed too
407 # TODO(lm): Find a common place to unify this functionality.
408 def format_plugin_output(output):
409 ok_marker = '<b class="stmarkOK">OK</b>'
410 warn_marker = '<b class="stmarkWARNING">WARN</b>'
411 crit_marker = '<b class="stmarkCRITICAL">CRIT</b>'
412 unknown_marker = '<b class="stmarkUNKNOWN">UNKN</b>'
414 output = output.replace("(!)", warn_marker) \
415 .replace("(!!)", crit_marker) \
416 .replace("(?)", unknown_marker) \
417 .replace("(.)", ok_marker)
419 return output
422 # TODO: Use cmk.utils.render.*?
423 def get_readable_rel_date(timestamp):
424 try:
425 change = int(timestamp)
426 except:
427 change = 0
428 rel_time = time.time() - change
429 seconds = rel_time % 60
430 rem = rel_time / 60
431 minutes = rem % 60
432 hours = (rem % 1440) / 60
433 days = rem / 1440
434 return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
437 def event_match_rule(rule, context):
438 return \
439 event_match_site(rule, context) or \
440 event_match_folder(rule, context) or \
441 event_match_hosttags(rule, context) or \
442 event_match_hostgroups(rule, context) or \
443 event_match_servicegroups(rule, context) or \
444 event_match_exclude_servicegroups(rule, context) or \
445 event_match_servicegroups(rule, context, is_regex = True) or \
446 event_match_exclude_servicegroups(rule, context, is_regex = True) or \
447 event_match_contacts(rule, context) or \
448 event_match_contactgroups(rule, context) or \
449 event_match_hosts(rule, context) or \
450 event_match_exclude_hosts(rule, context) or \
451 event_match_services(rule, context) or \
452 event_match_exclude_services(rule, context) or \
453 event_match_plugin_output(rule, context) or \
454 event_match_checktype(rule, context) or \
455 event_match_timeperiod(rule) or \
456 event_match_servicelevel(rule, context)
459 def event_match_site(rule, context):
460 if "match_site" not in rule:
461 return
463 required_site_ids = rule["match_site"]
465 # Fallback to local site ID in case there is none in the context
466 site_id = context.get("OMD_SITE", cmk.omd_site())
468 if site_id not in required_site_ids:
469 return "The site '%s' is not in the required sites list: %s" % \
470 (site_id, ",".join(required_site_ids))
473 def event_match_folder(rule, context):
474 if "match_folder" in rule:
475 mustfolder = rule["match_folder"]
476 mustpath = mustfolder.split("/")
477 hasfolder = None
478 for tag in context.get("HOSTTAGS", "").split():
479 if tag.startswith("/wato/"):
480 hasfolder = tag[6:].rstrip("/")
481 haspath = hasfolder.split("/")
482 if mustpath == [
485 return # Match is on main folder, always OK
486 while mustpath:
487 if not haspath or mustpath[0] != haspath[0]:
488 return "The rule requires WATO folder '%s', but the host is in '%s'" % (
489 mustfolder, hasfolder)
490 mustpath = mustpath[1:]
491 haspath = haspath[1:]
493 if hasfolder is None:
494 return "The host is not managed via WATO, but the rule requires a WATO folder"
497 def event_match_hosttags(rule, context):
498 required = rule.get("match_hosttags")
499 if required:
500 tags = context.get("HOSTTAGS", "").split()
501 if not config.hosttags_match_taglist(tags, required):
502 return "The host's tags %s do not match the required tags %s" % ("|".join(tags),
503 "|".join(required))
506 def event_match_servicegroups(rule, context, is_regex=False):
507 if is_regex:
508 match_type, required_groups = rule.get("match_servicegroups_regex", (None, None))
509 else:
510 required_groups = rule.get("match_servicegroups")
512 if context["WHAT"] != "SERVICE":
513 if required_groups:
514 return "This rule requires membership in a service group, but this is a host notification"
515 return
517 if required_groups is not None:
518 sgn = context.get("SERVICEGROUPNAMES")
519 if sgn is None:
520 return "No information about service groups is in the context, but service " \
521 "must be in group %s" % ( " or ".join(required_groups))
522 if sgn:
523 servicegroups = sgn.split(",")
524 else:
525 return "The service is in no service group, but %s%s is required" % (
526 (is_regex and "regex " or ""), " or ".join(required_groups))
528 for group in required_groups:
529 if is_regex:
530 r = regex(group)
531 for sg in servicegroups:
532 match_value = config.define_servicegroups[
533 sg] if match_type == "match_alias" else sg
534 if r.search(match_value):
535 return
536 elif group in servicegroups:
537 return
539 if is_regex:
540 if match_type == "match_alias":
541 return "The service is only in the groups %s. None of these patterns match: %s" % (
542 '"' + '", "'.join(config.define_servicegroups[x] for x in servicegroups) + '"',
543 '"' + '" or "'.join(required_groups)) + '"'
545 return "The service is only in the groups %s. None of these patterns match: %s" % (
546 '"' + '", "'.join(servicegroups) + '"', '"' + '" or "'.join(required_groups)) + '"'
548 return "The service is only in the groups %s, but %s is required" % (
549 sgn, " or ".join(required_groups))
552 def event_match_exclude_servicegroups(rule, context, is_regex=False):
553 if is_regex:
554 match_type, excluded_groups = rule.get("match_exclude_servicegroups_regex", (None, None))
555 else:
556 excluded_groups = rule.get("match_exclude_servicegroups")
558 if context["WHAT"] != "SERVICE":
559 # excluded_groups do not apply to a host notification
560 return
562 if excluded_groups is not None:
563 context_sgn = context.get("SERVICEGROUPNAMES")
564 if context_sgn is None:
565 # No actual groups means no possible negative match
566 return
568 servicegroups = context_sgn.split(",")
570 for group in excluded_groups:
571 if is_regex:
572 r = regex(group)
573 for sg in servicegroups:
574 match_value = config.define_servicegroups[
575 sg] if match_type == "match_alias" else sg
576 match_value_inverse = sg if match_type == "match_alias" else config.define_servicegroups[
579 if r.search(match_value):
580 return "The service group \"%s\" (%s) is excluded per regex pattern: %s" %\
581 (match_value, match_value_inverse, group)
582 elif group in servicegroups:
583 return "The service group %s is excluded" % group
586 def event_match_contacts(rule, context):
587 if "match_contacts" not in rule:
588 return
590 required_contacts = rule["match_contacts"]
591 contacts_text = context["CONTACTS"]
592 if not contacts_text:
593 return "The object has no contact, but %s is required" % (" or ".join(required_contacts))
595 contacts = contacts_text.split(",")
596 for contact in required_contacts:
597 if contact in contacts:
598 return
600 return "The object has the contacts %s, but %s is required" % (contacts_text,
601 " or ".join(required_contacts))
604 def event_match_contactgroups(rule, context):
605 required_groups = rule.get("match_contactgroups")
606 if required_groups is None:
607 return
609 if context["WHAT"] == "SERVICE":
610 cgn = context.get("SERVICECONTACTGROUPNAMES")
611 else:
612 cgn = context.get("HOSTCONTACTGROUPNAMES")
614 if cgn is None:
615 return
616 if not cgn:
617 return "The object is in no group, but %s is required" % (" or ".join(required_groups))
619 contactgroups = cgn.split(",")
620 for group in required_groups:
621 if group in contactgroups:
622 return
624 return "The object is only in the groups %s, but %s is required" % (
625 cgn, " or ".join(required_groups))
628 def event_match_hostgroups(rule, context):
629 required_groups = rule.get("match_hostgroups")
630 if required_groups is not None:
631 hgn = context.get("HOSTGROUPNAMES")
632 if hgn is None:
633 return "No information about host groups is in the context, but host " \
634 "must be in group %s" % ( " or ".join(required_groups))
635 if hgn:
636 hostgroups = hgn.split(",")
637 else:
638 return "The host is in no group, but %s is required" % (" or ".join(required_groups))
640 for group in required_groups:
641 if group in hostgroups:
642 return
644 return "The host is only in the groups %s, but %s is required" % (
645 hgn, " or ".join(required_groups))
648 def event_match_hosts(rule, context):
649 if "match_hosts" in rule:
650 hostlist = rule["match_hosts"]
651 if context["HOSTNAME"] not in hostlist:
652 return "The host's name '%s' is not on the list of allowed hosts (%s)" % (
653 context["HOSTNAME"], ", ".join(hostlist))
656 def event_match_exclude_hosts(rule, context):
657 if context["HOSTNAME"] in rule.get("match_exclude_hosts", []):
658 return "The host's name '%s' is on the list of excluded hosts" % context["HOSTNAME"]
661 def event_match_services(rule, context):
662 if "match_services" in rule:
663 if context["WHAT"] != "SERVICE":
664 return "The rule specifies a list of services, but this is a host notification."
665 servicelist = rule["match_services"]
666 service = context["SERVICEDESC"]
667 if not config.in_extraconf_servicelist(servicelist, service):
668 return "The service's description '%s' does not match by the list of " \
669 "allowed services (%s)" % (service, ", ".join(servicelist))
672 def event_match_exclude_services(rule, context):
673 if context["WHAT"] != "SERVICE":
674 return
675 excludelist = rule.get("match_exclude_services", [])
676 service = context["SERVICEDESC"]
677 if config.in_extraconf_servicelist(excludelist, service):
678 return "The service's description '%s' matches the list of excluded services" \
679 % context["SERVICEDESC"]
682 def event_match_plugin_output(rule, context):
683 if "match_plugin_output" in rule:
684 r = regex(rule["match_plugin_output"])
686 if context["WHAT"] == "SERVICE":
687 output = context["SERVICEOUTPUT"]
688 else:
689 output = context["HOSTOUTPUT"]
690 if not r.search(output):
691 return "The expression '%s' cannot be found in the plugin output '%s'" % \
692 (rule["match_plugin_output"], output)
695 def event_match_checktype(rule, context):
696 if "match_checktype" in rule:
697 if context["WHAT"] != "SERVICE":
698 return "The rule specifies a list of Check_MK plugins, but this is a host notification."
699 command = context["SERVICECHECKCOMMAND"]
700 if not command.startswith("check_mk-"):
701 return "The rule specified a list of Check_MK plugins, but his is no Check_MK service."
702 plugin = command[9:]
703 allowed = rule["match_checktype"]
704 if plugin not in allowed:
705 return "The Check_MK plugin '%s' is not on the list of allowed plugins (%s)" % \
706 (plugin, ", ".join(allowed))
709 def event_match_timeperiod(rule):
710 if "match_timeperiod" in rule:
711 timeperiod = rule["match_timeperiod"]
712 if timeperiod != "24X7" and not cmk_base.core.check_timeperiod(timeperiod):
713 return "The timeperiod '%s' is currently not active." % timeperiod
716 def event_match_servicelevel(rule, context):
717 if "match_sl" in rule:
718 from_sl, to_sl = rule["match_sl"]
719 if context['WHAT'] == "SERVICE" and context.get('SVC_SL', '').isdigit():
720 sl = saveint(context.get('SVC_SL'))
721 else:
722 sl = saveint(context.get('HOST_SL'))
724 if sl < from_sl or sl > to_sl:
725 return "The service level %d is not between %d and %d." % (sl, from_sl, to_sl)
728 def add_context_to_environment(plugin_context, prefix):
729 for key in plugin_context:
730 os.putenv(prefix + key, plugin_context[key].encode('utf-8'))
733 def remove_context_from_environment(plugin_context, prefix):
734 for key in plugin_context:
735 os.unsetenv(prefix + key)
738 # recursively turns a python object (with lists, dictionaries and pods) containing parameters
739 # into a flat contextlist for use as environment variables in plugins
741 # this: { "LVL1": [{"VALUE": 42}, {"VALUE": 13}] }
742 # would be added as:
743 # PARAMETER_LVL1_1_VALUE = 42
744 # PARAMETER_LVL1_2_VALUE = 13
745 def add_to_event_context(plugin_context, prefix, param, log_function):
746 if isinstance(param, list):
747 plugin_context[prefix + "S"] = " ".join(param)
748 for nr, value in enumerate(param):
749 add_to_event_context(plugin_context, "%s_%d" % (prefix, nr + 1), value, log_function)
750 elif isinstance(param, dict):
751 for key, value in param.items():
752 varname = "%s_%s" % (prefix, key.upper())
754 if varname == "PARAMETER_PROXY_URL":
755 # Compatibility for 1.5 pushover explicitly configured proxy URL format
756 if isinstance(value, str):
757 value = ("url", value)
759 value = config.get_http_proxy(value)
760 if value is None:
761 continue
763 add_to_event_context(plugin_context, varname, value, log_function)
764 else:
765 plugin_context[prefix] = plugin_param_to_string(param)
768 def plugin_param_to_string(value):
769 if isinstance(value, six.string_types):
770 return value
771 elif isinstance(value, (int, float)):
772 return str(value)
773 elif value is None:
774 return ""
775 elif value is True:
776 return "yes"
777 elif value is False:
778 return ""
779 elif isinstance(value, (tuple, list)):
780 return "\t".join(value)
782 return repr(value) # Should never happen
785 # int() function that return 0 for strings the
786 # cannot be converted to a number
787 # TODO: Clean this up!
788 def saveint(i):
789 try:
790 return int(i)
791 except:
792 return 0