def next_summary_timestring(): """Calculate timestring of next summary. SummaryDays 0 1 2 3 4 5 6 # Days of week. 0 = Sunday SummaryTime 9:45 # 24 hour clock NOTE: May be off by 1 hour over a DTS changes. """ try: target_hour = int(getcfg("SummaryTime", "").split(":")[0]) target_minute = int(getcfg("SummaryTime", "").split(":")[1]) today_day_num = datetime.datetime.today().isoweekday( ) # Sunday = 0, Saturday = 6 now = datetime.datetime.now().replace(second=0, microsecond=0) next_summary = now + datetime.timedelta(days=30) for daynum in getcfg("SummaryDays").split(): offset_num_days = int(daynum) - today_day_num if offset_num_days < 0: offset_num_days += 7 plus_day = now + datetime.timedelta(days=offset_num_days) with_time = plus_day.replace(hour=target_hour, minute=target_minute) if with_time < datetime.datetime.now(): with_time = with_time + datetime.timedelta(days=7) if with_time < next_summary: next_summary = with_time logging.debug(f"Next summary: {next_summary}") return next_summary except Exception as e: _msg = f"SummaryDays <{getcfg('SummaryDays','')}> or SummaryTime <{getcfg('SummaryTime','')}> settings could not be parsed\n {e}" logging.error(f"ERROR: {_msg}") raise ValueError(_msg) from None
def check_LAN_access(): """Check for basic access to another reliable host on the LAN, typically the router/gateway. Used as an execution gate for all non-local items on each RecheckInterval. Requires "Gateway <IP address or hostname>" definition in the config file. Returns True if the "Gateway" host can be pinged, else False. """ ip_or_hostname = getcfg("Gateway", False) if not ip_or_hostname: logging.error( f" ERROR: PARAMETER 'Gateway' NOT DEFINED IN CONFIG FILE - Aborting." ) sys.exit(1) if (IP_RE.match(ip_or_hostname) is None) and (HOSTNAME_RE.match(ip_or_hostname) is None): logging.error( f" ERROR: INVALID IP ADDRESS OR HOSTNAME <{ip_or_hostname}> - Aborting." ) sys.exit(1) pingrslt = cmd_check(["ping", "-c", "1", "-W", "1", getcfg("Gateway")], user_host_port="local", return_type="cmdrun") if pingrslt[0]: return True else: return False
def summary(self): logging.debug (f"Entering: {HANDLER_NAME}.summary") if (self.next_summary < datetime.datetime.now()) or globvars.args.once: sum = "" if len(self.events) == 0: sum += " No current events. All is well." else: for event in self.events: sum += f"{self.events[event]['message']}\n" if globvars.args.once: logging.debug(f"lanmonitor status summary\n{sum}") return snd_email(subj="lanmonitor status summary", body=sum, to=getcfg("EmailTo"), log=True) if getcfg("LogSummary", False): logging.warning(f"Summary:\n{sum}") self.next_summary = next_summary_timestring()
def log_event (self, dict): """ Handle any logging for each event status type Passed in dict keys: notif_key - Corresponds to the monitortype_key in the config file rslt RTN_PASS - Clears any prior logged WARNING / FAIL / CRITICAL events RTN_WARNING - Logged and included in summary, but no notification. RTN_FAIL - Logged & notified RTN_CRITICAL - Logged & notified, with periodic renotification message - Message text from the monitor plugin """ if dict["rslt"] == RTN_PASS: logging.info(dict["message"]) if dict["notif_key"] in self.events: del self.events[dict["notif_key"]] logging.warning(f" Event {dict['notif_key']} now passing. Removed from events log.") return if dict["rslt"] == RTN_WARNING: if dict["notif_key"] not in self.events: logging.warning(dict["message"]) else: # RTN_FAIL and RTN_CRITICAL cases if dict["rslt"] == RTN_CRITICAL: # if there are no prior active criticals, then set renotif time to now + renotif value if self.next_renotif < datetime.datetime.now() and not self.are_criticals(): self.next_renotif = datetime.datetime.now().replace(microsecond=0) + datetime.timedelta(seconds=convert_time(getcfg("CriticalReNotificationInterval"))[0]) if not globvars.args.once: logging.info(f"Next critical renotification: {self.next_renotif}") if dict["notif_key"] not in self.events and not globvars.args.once: snd_notif (subj=NOTIF_SUBJ, msg=dict["message"], log=True) if dict["notif_key"] not in self.events or globvars.args.once or getcfg("LoggingLevel", 30) < 30: logging.warning(dict["message"]) self.events[dict["notif_key"]] = {"message": dict["message"], "criticality": dict["rslt"]}
def renotif(self): """ Periodically send a consolidated notification with all current critical events if renotif time passed then if there are active criticals then send consolidated renotif message else set renotif time = now, which allows next critical to be notified immediately """ logging.debug (f"Entering: {HANDLER_NAME}.renotif") if (self.next_renotif < datetime.datetime.now()): logging.debug (f"self.next_renotif: {self.next_renotif}") logging.debug (f"datetime.datetime.now(): {datetime.datetime.now()}") if self.are_criticals(): criticals = "" for event in self.events: if self.events[event]["criticality"] == RTN_CRITICAL: criticals += f" {self.events[event]['message']}\n" snd_notif (subj=NOTIF_SUBJ, msg=criticals, log=True) self.next_renotif = datetime.datetime.now().replace(microsecond=0) + datetime.timedelta(seconds=convert_time(getcfg("CriticalReNotificationInterval"))[0]) logging.info(f"Next critical renotification: {self.next_renotif}") else: self.next_renotif = datetime.datetime.now().replace(microsecond=0)
def cmd_check(cmd, user_host_port, return_type=None, check_line_text=None, expected_text=None, not_text=None): """ Runs the cmd and operates on the response based on return_type. return_types: check_string Returns True if expected_text occurs in response of the cmd, plus the full full subprocess run structure check_line_text If provided, only the first line containing this text is checked expected_text Text that must be found not_text Text that must NOT be found cmdrun Returns True if the cmd return code was 0, plus the full full subprocess run structure """ if user_host_port != "local": u_h, _, port = split_user_host_port(user_host_port) cmd = ["ssh", u_h, "-p" + port, "-o", "ConnectTimeout=1", "-T"] + cmd for nTry in range(getcfg('nRetries')): try: logging.debug( f"cmd_check subprocess command try {nTry+1}: <{cmd}>") # runtry = subprocess.run(cmd, capture_output=True, text=True) # Py 3.7+ runtry = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) #Py3.6 requires old-style params except Exception as e: logging.error( f"ERROR: subprocess.run of cmd <{cmd}> failed.\n {e}") return False, None if return_type == "check_string": if check_line_text is None: text_to_check = runtry.stdout else: text_to_check = "" for line in runtry.stdout.split("\n"): if check_line_text in line: text_to_check = line break if expected_text in text_to_check: if not_text is not None: if not_text not in text_to_check: return True, runtry else: return True, runtry else: if nTry == getcfg('nRetries') - 1: return False, runtry elif return_type == "cmdrun": if runtry.returncode == 0: return True, runtry elif nTry == getcfg('nRetries') - 1: return False, runtry else: _msg = f"Invalid return_type <{return_type}> passed to cmd_check" logging.error(f"ERROR: {_msg}") raise ValueError(_msg) time.sleep(convert_time(getcfg('RetryInterval'))[0])