summaryrefslogblamecommitdiffstats
path: root/main.py
blob: b6096d29a4dfe238b59871eb3107f15426b0b376 (plain) (tree)
1
2
3
4
5
6
7
8
                  



               
          
                 
            


                                                      


                     




















                                                                                                                                            
                                                           








                                                                            
                                                                      

















                                                         
                                                     




                                  

                                 
                                   
                                     
                 





                                              
                                           
                                                                                  
       
                                             




                                                                          
                                                 



                                                                        
                                               

                                                                       
                                             

                                    






                                                                                             
 

                                                
                                                    













                                                                                                                                    
                                              

                                                  
                                                                       




                                                

                                                                     
 

                                                 
      
                       
                                                                                                                                                

                                                     
                            
                                                                                                                                                
                                                     
 



                                                                                



                                                                                                                                                                                              
                                             
                              






                                                                                                                                         
                                           

                             


                                                              
                   
                                         
          
                                       




                                                                                                                                                 
  




                                    
                                       

                                                                                             

                                          



                                                                              





                                                                                                                   

                                
                                                                                                         
 




















                                          
                                                                                                 



                                                     



                    
            





                                          
                     



                                                                       
                                                       










                                           

                                





                                                                                                           


                                     






                                               
                                                                                              
                                                                                                        
                                                                                                                  


                                                                                                          
                                                                                                                         
                                             
 
                                                                  
                                                           

                                
          
                 
                                                                                                                               


             
#!/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.wait()
  exit_code = response.returncode
  response = response.communicate()
  error = response[1].decode('utf-8')
  # Error happend
  if error != '' or exit_code != 0:
    err_msg = ''
    if exit_code == 1:
      err_msg = 'Destination Host Unreachable'
    elif exit_code == 2:
      err_msg = error.rstrip()
    print('\033[91m' + 'error' + '\033[0m')
    statusList.append(Status(name, hostname, 'error', 'Offline', 'ping', err_msg))
  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.SERVER_AUTH)
    ssl_factory.load_default_certs()

  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'
is_offline_class = ''
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'
  if status.status == 'Offline':
    is_offline_class = 'pulse'
  code += ('<div class="content_item"><div class="content_item_part"><div class="content_item_part_title">'
           + status.name + '</div><div class="content_item_part_subtitle">'
           + status.host + '</div></div><div class="content_item_part '
           + status.state  + '"><div class="content_item_part_title">'
           + status.status + '</div><div class="content_item_part_subtitle">'
           + timeString  + '</div></div></div>\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 += ('<div class="log_item"><div class="log_item_part time"><div class="content_item_part_title">['
          + parsed_date + ']</div></div><div class="log_item_part service"><div class="content_item_part_title">['
          + entry.service + ']</div></div><div class="log_item_part '
          + entry.state + '"><div class="content_item_part_title">['
          + entry.state + ']</div></div><div class="log_item_part"><div class="content_item_part_title">['
          + entry.host +']</div></div><div class="log_item_part"><div class="content_item_part_title log_item_part_msg">'
          + entry.msg + '</div></div></div>')

# 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')
pulse = ''
for line in html:
  html2.write(line.replace('%CONTENT%', code).replace('%LOG%', log).replace('%NOW%', now).replace('%PULSE%', is_offline_class))

html.close()
html2.close()