#!/usr/bin/python3 import datetime import logging import os import shutil import ssl import subprocess import tftpy import thriftpy from thriftpy.rpc import make_client from thriftpy.transport import TFramedTransportFactory import time import urllib.error import urllib.request # Global variables statusList = [] logEntries = [] newLogIndex = 0 # Class of the status objects class Status: def __init__(self, name, host, state, status, service, msg = '', data = []): # Name of the service. self.name = name # Hostname (Domain, ip, ...) self.host = host # State of the response. (success, warning, error) <-- This class is responsible for the coloring (green, orange, red) self.state = state # Status of the check. (Online, Offline, Temporaily not available, Some services unavailable, ...) <-- This text is shown on the website self.status = status # Type of the service. (ping, https, tftp, thrift, ...) self.service = service # Message e.g. error message self.msg = msg # Data the request return e.g. organisation list from the thrift client. self.data = data class LogEntry: def __init__(self, date, service, state, host, msg = '', data = []): self.date = date self.host = host self.service = service self.state = state self.msg = msg self.data = data class Organisation: def __init__(self, id, name): self.id = id self.name = name def __repr__(self): return str(self.__dict__) # Check connection functions. def ping(name, hostname): # Ping a hostname and tell if the server is up or down. print('Ping request ' + hostname + ' ... ', end='') response = subprocess.Popen( ['ping', '-c', '1', hostname], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) response = response.communicate() error = response[1].decode('utf-8') # Error happend if error != '': print('\033[91m' + 'error' + '\033[0m') statusList.append(Status(name, hostname, 'error', 'Offline', 'ping', error.rstrip())) else: print('\033[92m' + 'success' + '\033[0m') statusList.append(Status(name, hostname, 'success', 'Online', 'ping')) logStatus(statusList[-1]) def https(name, url): print('HTTPS request ' + url + ' ... ', end='') try: r = urllib.request.urlopen(url) if r.getcode() == 200: statusList.append(Status(name, url, 'success', 'Online', 'https')) print('\033[92m' + 'success' + '\033[0m') else: statusList.append(Status(name, url, 'error', 'Offline', 'https')) print('\033[91m' + 'error' + '\033[0m') except urllib.error.URLError as e: statusList.append(Status(name, url, 'error', 'Offline', 'https', msg=str(e))) print('\033[91m' + 'error' + '\033[0m') except ValueError: statusList.append(Status(name, url, 'error', 'Offline', 'https', msg="Unknown url type")) print('\033[91m' + 'error' + '\033[0m') finally: logStatus(statusList[-1]) def tftp(name, host, port, filename): print('TFTP request ' + host + ' ...', end='') hostname = host + ':' + str(port) + ':' + filename try: client = tftpy.TftpClient(host, port) client.download(filename, 'tmp_tftp_file') print('\033[92m' + 'success' + '\033[0m') # Delete tmp tftp file os.remove('tmp_tftp_file') statusList.append(Status(name, hostname, 'success', 'Online', 'tftp', msg='File ' + filename + ' downloaded successfully.')) except Exception as e: print('\033[91m' + 'error' + '\033[0m') statusList.append(Status(name, hostname, 'error', 'Offline', 'tftp', msg=str(e))) finally: logStatus(statusList[-1]) # Creates the thrift client and prwlp-pxe.ruf.uni-freiburg.de .ceeds the get Organisations call. Server can either be SAT or MASTER def thrift(name, ip, port, server, SSL=False): host = ip + ':' + str(port) print('THRIFT request ' + host + ' ...', end='') bwlp_thrift = thriftpy.load('bwlp.thrift', module_name='bwlp_thrift') # SSL factory ssl_factory = None if SSL == 'true': # ssl_factory = ssl.create_default_context() ssl_factory = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) organisations = [] # Different clients for SAT / Master is needed. try: if server == 'SAT': satserver = make_client(bwlp_thrift.SatelliteServer, host=ip, port=port, trans_factory=TFramedTransportFactory(), ssl_context=ssl_factory) organisations = satserver.getAllOrganizations() elif server == 'MASTER': masterserver = make_client(bwlp_thrift.MasterServer, host=ip, port=port, trans_factory=TFramedTransportFactory(), ssl_context=ssl_factory) organisations = masterserver.getOrganizations() organisationList = [] for org in organisations: organisationList.append(Organisation(org.organizationId, org.displayName)) if len(organisationList) == 0: statusList.append(Status(name, host, 'warning', 'Online (' + str(len(organisationList)) + ')', 'thrift', msg=str(len(organisationList)) + " organizations found",data=organisationList)) else: statusList.append(Status(name, host, 'success', 'Online (' + str(len(organisationList)) + ')', 'thrift', msg=str(len(organisationList)) + " organizations found",data=organisationList)) print('\033[92m' + 'success' + '\033[0m') except ConnectionResetError: statusList.append(Status(name, host, 'error', 'Offline', 'thrift', msg='ConnectionResetError: [Errno 104] Connection reset by peer')) print('\033[91m' + 'error' + '\033[0m') except FileNotFoundError: statusList.append(Status(name, host, 'error', 'Offline', 'thrift', msg='SSL path incorrect (FileNotFound)')) print('\033[91m' + 'error' + '\033[0m') except Exception as e: statusList.append(Status(name, host, 'error', 'Offline', 'thrift', msg=str(e))) print('\033[91m' + 'error' + '\033[0m') finally: logStatus(statusList[-1]) # Parses the log from the logfile. Fills the logEntries array. def parseLog(): global logEntries if not (os.path.exists('history.txt')): return with open('history.txt', 'r') as log: for line in log: line = line.strip() entry = line.split('\t') if len(entry) < 4: continue logEntries.append(LogEntry(entry[0], entry[1], entry[2], entry[3], '' if len(entry) < 5 else entry[4], [] if len(entry) < 6 else entry[5])) global newLogIndex newLogIndex = len(logEntries) # Writes updated log in the logfile. def writeLog(): with open('history.txt', 'a') as log: for entry in logEntries[newLogIndex:]: log_string = entry.date + '\t' + entry.service + '\t' + entry.state + '\t' + entry.host log_string += '\t' + str(entry.msg) log_string += '\t' + str(entry.data) log.write(log_string + '\n') # Returns the most recent log object to a given status. None if there is none. def getLogEntry(status): return next((x for x in reversed(logEntries) if (x.host == status.host) and (x.service == status.service)), None) # Checks weather the status has to be logged or not. (Does the status has changes from the last time?) def logStatus(status): obj = getLogEntry(status) if (obj is None) or (status.state != obj.state): # Save unix timestamps date = str(int(time.time())) logEntries.append(LogEntry(date, status.service, status.state, status.host, status.msg, status.data)) # Reads the config and executes the calls. def readConfig(): if not (os.path.exists('bwlp.config')): return with open('bwlp.config', 'r') as config: for line in config: line = line.strip() entry = line.split('\t') if entry[0] == '#': continue check(entry) # Calls the check method. def check(entry): if entry[0] == 'ping': if (len(entry) < 3): return ping(entry[1], entry[2]) elif entry[0] == 'https': if (len(entry) < 3): return https(entry[1], entry[2]) elif entry[0] == 'thrift': if (len(entry) < 5): return thrift(entry[1], entry[2], int(entry[3]), entry[4], entry[5] if len(entry) == 6 else 'false') elif entry[0] == 'tftp': if (len(entry) < 5): return tftp(entry[1], entry[2], int(entry[3]), entry[4]) # Parse the logfile. parseLog() # Call the checks. readConfig() # Write the new logfile. writeLog() # HTML Processing to generate the website. code = '\r\n' for status in statusList: # Prepare and calculate the time since the server is online / offline obj = getLogEntry(status) now = datetime.datetime.now() date = datetime.datetime.fromtimestamp(int(obj.date)) time = now - date days = time.days hours = time.seconds // 3600 minutes = (time.seconds // 60) % 60 timeString = '' if days > 0: timeString += str(days) + ' days ' if hours > 0: timeString += str(hours) + ' hours ' if minutes > 0: timeString += str(minutes) + ' minutes' code += ('
' + status.name + '
' + status.host + '
' + status.status + '
' + timeString + '
\r\n') # Generate the html code for the log. log = '\r\n' # Cut the log to the last n entries n = 500 cut_amount = 0 if len(logEntries) >= n: cut_amount = len(logEntries) - n for entry in reversed(logEntries[cut_amount:]): parsed_date = datetime.datetime.fromtimestamp(int(entry.date)).strftime('%Y-%m-%d %H:%M:%S') log += ('
[' + parsed_date + ']
[' + entry.service + ']
[' + entry.state + ']
[' + entry.host +']
' + entry.msg + '
') # Replace the %CONTENT% in the template with the actual html code. now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') html = open('template.html') html2 = open('status.html', 'w') for line in html: html2.write(line.replace('%CONTENT%', code).replace('%LOG%', log).replace('%NOW%', now)) html.close() html2.close()