def setup(self, item): """ Set up instance vars and check item values. Passed in item dictionary keys: key Full 'itemtype_tag' key value from config file line tag 'tag' portion only from 'itemtype_tag' from config file line user_host_port 'local' or 'user@hostname[:port]' from config file line host 'local' or 'hostname' from config file line critical True if 'CRITICAL' is in the config file line rest_of_line Remainder of line after the 'user_host' from the config file line Returns True if all good, else False """ # Construct item type specifics and check validity self.key = item["key"] # vvvv These items don't need to be modified self.key_padded = self.key.ljust(globvars.keylen) self.tag = item["tag"] self.user_host_port = item["user_host_port"] self.host = item["host"] self.host_padded = self.host.ljust(globvars.hostlen) if item["critical"]: self.failtype = RTN_CRITICAL self.failtext = "CRITICAL" else: self.failtype = RTN_FAIL self.failtext = "FAIL" # ^^^^ These items don't need to be modified self.expected_mode = item["rest_of_line"] if self.expected_mode not in SEMODES: logging.error( f" ERROR: <{self.key}> INVALID EXPECTED sestatus MODE <{self.expected_mode}> PROVIDED - EXPECTING <{SEMODES}>" ) return RTN_FAIL return RTN_PASS
def split_user_host_port(u_h_p): """ Handle variations in passed-in user_host_port (u_h_p) "local", "user@host", or "user@host:port" Return separate user_host, host, and port values EG: "xyz@host15:4455" returns: ["xyz@host15", "host15", "4455"] "xyz@host15" returns: ["xyz@host15", "host15", "22"] (the default port is "22" for ssh) "local" returns ["local", "local", ""] """ user_host_noport = u_h_p.split(":")[0] host = u_h_p port = "" if host != "local": port = "22" out = USER_HOST_FORMAT.match(u_h_p) if out: host = out.group(1) else: out = USER_HOST_PORT_FORMAT.match(u_h_p) if out: host = out.group(1) port = out.group(2) else: _msg = f"Expecting <user@host> or <user@host:port> format, but found <{u_h_p}>." logging.error(f"ERROR: {_msg}") raise ValueError(_msg) return user_host_noport, host, port
def convert_time(timeX): """ Given time value term such as 90s, 15m, 20h, 3d, 2w return int seconds and units. <1h> returns (3600, "hours") If no units suffix, the value is interpreted as secs. <90> returns (90, "secs ") """ if type(timeX) is int: return timeX, "secs " # Case Int try: return int(timeX), "secs " # Case Str without units except: pass try: # Case Str with units time_value = int(timeX[:-1]) time_unit = timeX[-1:].lower() if time_unit == "s": return time_value, "secs " if time_unit == "m": return time_value * 60, "mins " if time_unit == "h": return time_value * 60 * 60, "hours" if time_unit == "d": return time_value * 60 * 60 * 24, "days " if time_unit == "w": return time_value * 60 * 60 * 24 * 7, "weeks" raise ValueError(f"Illegal time units <{time_unit}>") except Exception as e: logging.error(f"ERROR: Could not convert time value <{timeX}>\n {e}") raise
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 setup (self, item): """ Set up instance vars and check item values. Passed in item dictionary keys: key Full 'itemtype_tag' key value from config file line tag 'tag' portion only from 'itemtype_tag' from config file line user_host_port 'local' or 'user@hostname[:port]' from config file line host 'local' or 'hostname' from config file line critical True if 'CRITICAL' is in the config file line rest_of_line Remainder of line after the 'user_host' from the config file line Returns True if all good, else False """ # Construct item type specifics and check validity self.key = item["key"] # vvvv These items don't need to be modified self.key_padded = self.key.ljust(globvars.keylen) self.tag = item["tag"] self.user_host_port = item["user_host_port"] self.host = item["host"] self.host_padded = self.host.ljust(globvars.hostlen) if item["critical"]: self.failtype = RTN_CRITICAL self.failtext = "CRITICAL" else: self.failtype = RTN_FAIL self.failtext = "FAIL" # ^^^^ These items don't need to be modified self.service_name = item["rest_of_line"] # Identify the system manager type - expecting systemd or init psp1_rslt = cmd_check(["ps", "-p1"], user_host_port=self.user_host_port, return_type="cmdrun") if not psp1_rslt[0]: logging.error (f" WARNING: <{self.key}> - {self.host} - COULD NOT READ SYSTEM MANAGER TYPE (ps -p1 run failed)") return RTN_WARNING if "systemd" in psp1_rslt[1].stdout: self.cmd = ["systemctl", "status", self.service_name] self.check_line_text="Active:" self.expected_text="active (running)" self.not_text=None elif "init" in psp1_rslt[1].stdout: self.cmd = ["service", self.service_name, "status"] self.check_line_text=None #"Active:" self.expected_text="running" self.not_text="not" else: logging.error (f" ERROR: <{self.key}> - {self.host} - UNKNOWN SYSTEM MANAGER TYPE") return RTN_FAIL return RTN_PASS
def setup(self, item): """ Set up instance vars and check item values. Passed in item dictionary keys: key Full 'itemtype_tag' key value from config file line tag 'tag' portion only from 'itemtype_tag' from config file line user_host_port 'local' or 'user@hostname[:port]' from config file line host 'local' or 'hostname' from config file line critical True if 'CRITICAL' is in the config file line rest_of_line Remainder of line after the 'user_host' from the config file line Returns True if all good, else False """ # Construct item type specifics and check validity self.key = item["key"] # vvvv These items don't need to be modified self.key_padded = self.key.ljust(globvars.keylen) self.tag = item["tag"] self.user_host_port = item["user_host_port"] self.host = item["host"] self.host_padded = self.host.ljust(globvars.hostlen) if item["critical"]: self.failtype = RTN_CRITICAL self.failtext = "CRITICAL" else: self.failtype = RTN_FAIL self.failtext = "FAIL" # ^^^^ These items don't need to be modified try: xx = item["rest_of_line"].split(maxsplit=1) self.maxage_sec, self.units = convert_time( xx[0]) # Exception on conversion error self.url = xx[1] + "/Info.htm" except Exception as e: logging.error( f" ERROR: <{self.key}> INVALID LINE SYNTAX <{item['rest_of_line']}>\n {e}" ) return RTN_FAIL return RTN_PASS
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])