diff --git a/README.md b/README.md index 597ad59..ed41a6c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # s2watchdog -A simple watchdog script for the deep freezer alert system in our s2 scullery. \ No newline at end of file +A simple watchdog script for the deep freezer alert system in our s2 scullery. + +The alert system of our deep freezer in the s2 scullery will send emails as +soon, as the temperature exceeds a certain level. + +This script will check regularly if the alert system itself is reachable and +send an email if the system is not available (network down or power failure). \ No newline at end of file diff --git a/s2watchdog/__init__.py b/s2watchdog/__init__.py index 966eb36..f6219c7 100644 --- a/s2watchdog/__init__.py +++ b/s2watchdog/__init__.py @@ -1,2 +1,128 @@ -def test(): - print('TESTRUN') +''' Simple watchdog script for the deep freezer alert system + +The alert system of our deep freezer in the s2 scullery will send emails as +soon, as the temperature exceeds a certain level. + +This script will check regularly if the alert system itself is reachable and +send an email if the system is not available (network down or power failure). +''' + +import json +import requests + +from datetime import datetime + +from . import pysema + + + +# alert emails recipients, subject and message +EMAIL_RECIPIENTS = ['frey@imtek.de'] +EMAIL_SUBJECT = '[S2 Scullery]: Deep Freezer Alert System Is Offline' +EMAIL_BODY = [ + 'The -80 alert system in the S2 scullery seems to be offline.', + 'Maybe the network is down, there is a power outage or the system crashed.', + '', + '>>> Please have a look as soon as possible. <<<', + '', + 'The system was last seen on {} (UTC)' + ] + +# which url to check +ALERT_SYSTEM_URL = 'http://test.cpi.imtek.uni-freiburg.de' + + +# How many times can an error be ignored? +GRACE_PERIOD = 4 + +# where to store the data and default values +DATA_STORE_PATH = '/val/local/s2watchdog.json' +DEFAULTS = { + 'emails_sent': 0, + 'last_seen': 'Never', + 'last_result': '', + 'time_to_live': 0, + 'url': ALERT_SYSTEM_URL, + } + +# how long (in seconds) to wait for a response from the alert system +TIMEOUT = 5 + + + +def load_data(path): + ''' load and decode the saved data ''' + try: + with open(path, 'r') as file_handle: + data = json.load(file_handle) + + # ensure, that these two keys are integers: + for key in ['time_to_live', 'emails_sent']: + value = data.get(key, None) + if not isinstance(value, int): + data[key] = 0 + return data + except (FileNotFoundError, AttributeError): + return DEFAULTS + + +def save_data(path, data=None) + data = data or DEFAULTS + with open(path, 'w') as file_handle: + json.dump(data, file_handle) + + +def query_alert_system(url): + response = requests.get( + url, + allow_redirects=False, + timeout=TIMEOUT + ) + response.raise_for_status() + return response.text + + +def send_alert_mail(data): + headers['Reply-To'] = EMAIL_RECIPIENTS + body = '\n'.join(EMAIL_BODY) + pysema.send( + EMAIL_RECIPIENTS, + EMAIL_SUBJECT, + body.format(data['last_seen']), + headers + ) + +def run(): + data = load_data(DATA_STORE_PATH) + + # the url has changed, use the default values + if data['url'] != ALERT_SYSTEM_URL: + data = DEFAULTS + + try: + result = query_alert_system(ALERT_SYSTEM_URL) + if result != data['last_result']: + # the retrieved result was not cached somehow + # everything is all right! + # we'd return after the data is saved + data = { + 'emails_sent': 0, + 'last_seen': datetime.utcnow().isoformat(), + 'last_result': result, + 'time_to_live': GRACE_PERIOD, + 'url': ALERT_SYSTEM_URL, + } + save_data(DATA_STORE_PATH, data) + return + except requests.exceptions.RequestException: + # the alarm system could not be reached. + # but maybe its in the grace period, so we pass here + pass + + ttl = data['time_to_live'] - 1 + if ttl <= 0: + data['email_sent'] = data['email_sent'] + 1 + data['time_to_live'] = GRACE_PERIOD * data['email_sent'] + save_data(DATA_STORE_PATH, data) + send_alert_mail(data) + diff --git a/s2watchdog/pysema.py b/s2watchdog/pysema.py index 4610b13..11b1099 100644 --- a/s2watchdog/pysema.py +++ b/s2watchdog/pysema.py @@ -21,12 +21,12 @@ class SendMailException(Exception): pass -def send(to=None, subject='', message='', **headers): - msg = create_message(to, subject, message, **headers) +def send(to=None, subject='', message='', headers=None): + msg = create_message(to, subject, message, headers) send_message(msg) -def create_message(to=None, subject='', message='', **headers): +def create_message(to=None, subject='', message='', headers=None): if isinstance(to, str): recipients = to elif isinstance(to, (list, set, tuple)): @@ -34,6 +34,9 @@ def create_message(to=None, subject='', message='', **headers): else: raise SendMailException('No recipients given') + if not isinstance(headers, dict): + headers = {} + # ensure that the 'from' keyword (if given) is uppercase sender_mail = headers.pop('from', None) if sender_mail is not None: diff --git a/setup.py b/setup.py index 4117d12..def3557 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,8 @@ with open(os.path.join(here, 'README.md')) as f: README = f.read() requires = [ -] + 'requests' + ] setup( name='s2watchdog', @@ -29,7 +30,7 @@ setup( install_requires=requires, entry_points={ 'console_scripts': [ - 's2watchdog = s2watchdog:test', - ], - }, -) + 's2watchdog = s2watchdog:run', + ], + }, + )