Skip to content

Commit

Permalink
Broke classes out into their own files. Fully tested the utilities me…
Browse files Browse the repository at this point in the history
…thods.
  • Loading branch information
erunks committed Jun 9, 2020
1 parent 26c4229 commit ee1c64f
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 172 deletions.
172 changes: 1 addition & 171 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,175 +1,5 @@
import asyncio
import ipdb
from os import getenv

class InternetStatusReporter:
def __init__(self):
from dotenv import load_dotenv, find_dotenv
from utils import get_addresses

load_dotenv(find_dotenv())
self.__setup_logger()

self.NETWORK_STATUS = {
'NORMAL': 0,
'DEGRADED': 1,
'DOWN': 2
}

default_host_addresses = [
'1.1.1.1',
'8.8.8.8',
'8.8.4.4',
'139.130.4.5'
]
default_latency_addresses = [
'amazon.com',
'cloudflare.com',
'google.com',
'youtube.com'
]

self.host_addresses = default_host_addresses + get_addresses(getenv('HOST_ADDRESSES'))
self.latency_addresses = default_latency_addresses + get_addresses(getenv('LATENCY_ADDRESSES'))
self.last_issue_at = -1
self.current_status = self.NETWORK_STATUS['NORMAL']
self.lock = asyncio.Lock()
self.db = None

def __del__(self):
if self.db != None:
self.db.close()

def __is_down(self):
return self.current_status != self.NETWORK_STATUS['NORMAL']

def __setup_logger(self):
from logging import ERROR, INFO, basicConfig, getLogger, Formatter
from mailHandler import MailHandler

logFormat = '%(asctime)s - %(levelname)s: %(message)s'
basicConfig(
filename=getenv('LOG_FILE'),
filemode='a',
format=logFormat,
level=INFO
)

mailHandler = MailHandler(
(getenv('SMTP_SERVER'), getenv('SMTP_PORT')),
getenv('PI_EMAIL'),
getenv('MAILTO').split(','),
'Internet Status Reporter: ERROR!',
(getenv('PI_EMAIL'), getenv('PI_EMAIL_PASSWORD'))
)
mailHandler.setLevel(ERROR)
mailHandler.setFormatter(Formatter(logFormat))

self.logger = getLogger('ISR_Logger')
self.logger.addHandler(mailHandler)
self.logger.info('InternetStatusReporter is starting up')

async def run(self):
loss = self.check_status()
info = self.measure_latency()
if self.__is_down():
await self.lock.acquire()
while(self.lock.locked()):
self.check_status()

if info == '':
info = self.measure_latency()

self.report_issue(loss, info)

def check_status(self):
from utils import calulate_percentage_lost, ping_hosts

percentage_lost = calulate_percentage_lost(
ping_hosts(self.host_addresses, getenv('PING_COUNT'))
)
self.update_status(percentage_lost)

return percentage_lost

def update_status(self, percentage_lost):
from datetime import datetime

if percentage_lost == 0.0:
if self.current_status != self.NETWORK_STATUS['NORMAL']:
self.logger.info('Internet is back to normal')
self.lock.release()

self.current_status = self.NETWORK_STATUS['NORMAL']
else:
if self.current_status == self.NETWORK_STATUS['NORMAL']:
self.logger.warning(f'Internet is degraded or down. Percentage lost at: {percentage_lost}')
self.last_issue_at = datetime.now()

if percentage_lost == 100.0:
self.current_status = self.NETWORK_STATUS['DOWN']
else:
self.current_status = self.NETWORK_STATUS['DEGRADED']

def measure_latency(self):
from json import dumps
from sys import exc_info
from tcp_latency import measure_latency
from utils import calculate_standard_deviation

results = {}
for latency_address in self.latency_addresses:
try:
runs = int(getenv('LATENCY_RUNS'))
data = measure_latency(host=latency_address, port=80, runs=runs, timeout=2.5)
mean, deviation = calculate_standard_deviation(data, self.logger)
except:
self.logger.exception(f'Unexpected error: {exc_info()[0]}')
else:
results[latency_address] = {
'deviation': deviation,
'max': max(data),
'mean': mean,
'min': min(data)
}

return dumps(results)

def report_issue(self, loss, info = ''):
from datetime import datetime
from mysql.connector import Error as DB_Error, ProgrammingError, connect, errorcode
from utils import get_downtime

try:
self.db = connect(
database = getenv('DATABASE'),
host = getenv('DB_HOST'),
password = getenv('DB_PASSWORD'),
user = getenv('DB_USERNAME')
)

cursor = self.db.cursor()

sql = 'INSERT INTO `outtages` (`loss`, `downtime`, `created_at`, `maintenance`, `info`) VALUES (%s, %s, %s, %s, %s)'
values = (loss, get_downtime(self.last_issue_at), datetime.now(), False, info)
cursor.execute(sql, values)

self.db.commit()
cursor.close()
except ProgrammingError as err:
if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
self.logger.error(f'Access denied: {err}')
else:
self.logger.exception(f'Unexpected error: {err}')
except DB_Error as err:
if err.errno == errorcode.ER_BAD_DB_ERROR:
self.logger.error(f'Database does not exist: {err}')
else:
self.logger.exception(f'Unexpected error: {err}')
finally:
if self.db != None:
self.db.close()
self.db = None
from src.InternetStatusReporter import InternetStatusReporter

if __name__ == "__main__":
isr = InternetStatusReporter()
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
freezegun
ipdb
mysql.connector
pingparsing
Expand Down
1 change: 1 addition & 0 deletions scripts/run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python3 -m unittest -v src/tests/*_test.py
172 changes: 172 additions & 0 deletions src/InternetStatusReporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import asyncio
import ipdb
from os import getenv

class InternetStatusReporter:
def __init__(self):
from dotenv import load_dotenv, find_dotenv
from utils import get_addresses

load_dotenv(find_dotenv())
self.__setup_logger()

self.NETWORK_STATUS = {
'NORMAL': 0,
'DEGRADED': 1,
'DOWN': 2
}

default_host_addresses = [
'1.1.1.1',
'8.8.8.8',
'8.8.4.4',
'139.130.4.5'
]
default_latency_addresses = [
'amazon.com',
'cloudflare.com',
'google.com',
'youtube.com'
]

self.host_addresses = default_host_addresses + get_addresses(getenv('HOST_ADDRESSES'))
self.latency_addresses = default_latency_addresses + get_addresses(getenv('LATENCY_ADDRESSES'))
self.last_issue_at = -1
self.current_status = self.NETWORK_STATUS['NORMAL']
self.lock = asyncio.Lock()
self.db = None

def __del__(self):
if self.db != None:
self.db.close()

def __is_down(self):
return self.current_status != self.NETWORK_STATUS['NORMAL']

def __setup_logger(self):
from logging import ERROR, INFO, basicConfig, getLogger, Formatter
from MailHandler import MailHandler

logFormat = '%(asctime)s - %(levelname)s: %(message)s'
basicConfig(
filename=getenv('LOG_FILE'),
filemode='a',
format=logFormat,
level=INFO
)

mailHandler = MailHandler(
(getenv('SMTP_SERVER'), getenv('SMTP_PORT')),
getenv('PI_EMAIL'),
getenv('MAILTO').split(','),
'Internet Status Reporter: ERROR!',
(getenv('PI_EMAIL'), getenv('PI_EMAIL_PASSWORD'))
)
mailHandler.setLevel(ERROR)
mailHandler.setFormatter(Formatter(logFormat))

self.logger = getLogger('ISR_Logger')
self.logger.addHandler(mailHandler)
self.logger.info('InternetStatusReporter is starting up')

async def run(self):
loss = self.check_status()
info = self.measure_latency()
if self.__is_down():
await self.lock.acquire()
while(self.lock.locked()):
self.check_status()

if info == '':
info = self.measure_latency()

self.report_issue(loss, info)

def check_status(self):
from utils import calulate_percentage_lost, ping_hosts

percentage_lost = calulate_percentage_lost(
ping_hosts(self.host_addresses, getenv('PING_COUNT'))
)
self.update_status(percentage_lost)

return percentage_lost

def update_status(self, percentage_lost):
from datetime import datetime

if percentage_lost == 0.0:
if self.current_status != self.NETWORK_STATUS['NORMAL']:
self.logger.info('Internet is back to normal')
self.lock.release()

self.current_status = self.NETWORK_STATUS['NORMAL']
else:
if self.current_status == self.NETWORK_STATUS['NORMAL']:
self.logger.warning(f'Internet is degraded or down. Percentage lost at: {percentage_lost}')
self.last_issue_at = datetime.now()

if percentage_lost == 100.0:
self.current_status = self.NETWORK_STATUS['DOWN']
else:
self.current_status = self.NETWORK_STATUS['DEGRADED']

def measure_latency(self):
from json import dumps
from sys import exc_info
from tcp_latency import measure_latency
from utils import calculate_standard_deviation

results = {}
for latency_address in self.latency_addresses:
try:
runs = int(getenv('LATENCY_RUNS'))
data = measure_latency(host=latency_address, port=80, runs=runs, timeout=2.5)
mean, deviation = calculate_standard_deviation(data, self.logger)
except:
self.logger.exception(f'Unexpected error: {exc_info()[0]}')
else:
results[latency_address] = {
'deviation': deviation,
'max': max(data),
'mean': mean,
'min': min(data)
}

return dumps(results)

def report_issue(self, loss, info = ''):
from datetime import datetime
from mysql.connector import Error as DB_Error, ProgrammingError, connect, errorcode
from utils import get_downtime

try:
self.db = connect(
database = getenv('DATABASE'),
host = getenv('DB_HOST'),
password = getenv('DB_PASSWORD'),
user = getenv('DB_USERNAME')
)

cursor = self.db.cursor()

sql = 'INSERT INTO `outtages` (`loss`, `downtime`, `created_at`, `maintenance`, `info`) VALUES (%s, %s, %s, %s, %s)'
values = (loss, get_downtime(self.last_issue_at), datetime.now(), False, info)
cursor.execute(sql, values)

self.db.commit()
cursor.close()
except ProgrammingError as err:
if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
self.logger.error(f'Access denied: {err}')
else:
self.logger.exception(f'Unexpected error: {err}')
except DB_Error as err:
if err.errno == errorcode.ER_BAD_DB_ERROR:
self.logger.error(f'Database does not exist: {err}')
else:
self.logger.exception(f'Unexpected error: {err}')
finally:
if self.db != None:
self.db.close()
self.db = None
File renamed without changes.
Loading

0 comments on commit ee1c64f

Please sign in to comment.