Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
webctrl/wc_checkAlarms.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
385 lines (343 sloc)
18.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
import suds | |
import sys | |
import xml | |
import argparse | |
import getpass | |
from alarm import alarm | |
from time import sleep | |
import datetime | |
from Email import getMailAddresses, sendEmails | |
from Environment import readSettings | |
def main(args): | |
#################################################################################################################### | |
### Check all arguments | |
#################################################################################################################### | |
errorMessage = "No path to SETTINGS file specified. Provide one via '-f my_settingsFile' or {..., 'settingsFile':'my_settingsFile', ...}" | |
try: | |
if args['settingsFile'] is None or not args['settingsFile']: | |
print(errorMessage) | |
sys.exit(1) | |
else: | |
settingsFile = args['settingsFile'] | |
except KeyError: | |
print(errorMessage) | |
sys.exit(1) | |
settings = readSettings(settingsFile) | |
errorMessage = "No username specified. Provide one via '-u my_username' or {..., 'username':'my_username', ...} or WC_USERNAME=my_username" | |
try: | |
if args['username'] is None or not args['username']: | |
if not settings['WC_USERNAME']: | |
print(errorMessage) | |
sys.exit(1) | |
else: | |
username = settings['WC_USERNAME'] | |
else: | |
username = str(args['username']) | |
except KeyError: | |
print(errorMessage) | |
sys.exit(1) | |
# The password cannot be given via environment variable | |
errorMessage = "No password specified. Provide one via {..., 'password':'my_password', ...}" | |
try: | |
if args['password'] is None or not args['password']: | |
print(errorMessage) | |
sys.exit(1) | |
else: | |
password = args['password'] | |
except KeyError: | |
print(errorMessage) | |
sys.exit(1) | |
verbosityDefault = False | |
errorMessage = "No verbosity specified. Provide one via '-v my_verbosity' or {..., 'verbosity':'my_verbosity', ...} or WC_VERBOSITY=my_verbosity. " \ | |
"Using a default now: '" + str(verbosityDefault) + "'" | |
try: | |
if args['verbosity'] is None or not args['verbosity']: | |
if not settings['WC_VERBOSITY']: | |
print(errorMessage) | |
verbosity = verbosityDefault | |
else: | |
verbosity = settings['WC_VERBOSITY'] | |
else: | |
verbosity = str(args['verbosity']) | |
except KeyError: | |
print(errorMessage) | |
verbosity = verbosityDefault | |
nodeDefault = '/trees/geographic' | |
errorMessage = "No node specified. Provide one via '-n my_node' or {..., 'node':'my_node', ...} or WC_NODE=my_node. " \ | |
"Using a default now: '" + nodeDefault + "'" | |
try: | |
if args['node'] is None or not args['node']: | |
if not settings['WC_NODE']: | |
print(errorMessage) | |
node = nodeDefault | |
else: | |
node = settings['WC_NODE'] | |
else: | |
node = str(args['node']) | |
except KeyError: | |
print(errorMessage) | |
node = nodeDefault | |
urlDefault = 'https://webctrl.rz-berlin.mpg.de' | |
errorMessage = "No URL specified. Provide one via '-url my_url' or {..., 'url':'my_url', ...} or WC_URL=my_url. " \ | |
"Using a default now: '" + urlDefault + "'" | |
try: | |
if args['url'] is None or not args['url']: | |
if not settings['WC_URL']: | |
print(errorMessage) | |
url = urlDefault | |
else: | |
url = settings['WC_URL'] | |
else: | |
url = str(args['url']) | |
except KeyError: | |
print(errorMessage) | |
url = urlDefault | |
wsdlFile = url + '/_common/webservices/Report?wsdl' | |
intervalDefault = 5.0 | |
errorMessage = "No interval specified. Provide one via '-i my_interval' or {..., 'interval':'my_interval', ...} or " \ | |
"WC_ALARMS_INTERVAL=my_interval. Using a default now: " + str(intervalDefault) + " minutes" | |
try: | |
if args['interval'] is None or not args['interval']: | |
if not settings['WC_ALARMS_INTERVAL']: | |
print(errorMessage) | |
interval = intervalDefault | |
else: | |
interval = float(settings['WC_ALARMS_INTERVAL']) | |
else: | |
interval = args['interval'] | |
except KeyError: | |
print(errorMessage) | |
interval = intervalDefault | |
if interval <= 0.0: | |
print 'The interval must be bigger than 0.0.' | |
sys.exit(1) | |
checkForNewAlarmsInterval = interval | |
takeActionDefault = 20.0 | |
errorMessage = "No time to take action specified. Provide one via '-a my_takeAction' or {..., 'takeAction':'my_takeAction', ...} " \ | |
"or WC_ALARMS_TAKEACTION=my_takeAction. Using a default now: " + str(takeActionDefault) + " minutes" | |
try: | |
if args['takeAction'] is None or not args['takeAction']: | |
if not settings['WC_ALARMS_TAKEACTION']: | |
print(errorMessage) | |
takeAction = takeActionDefault | |
else: | |
takeAction = float(settings['WC_ALARMS_TAKEACTION']) | |
else: | |
takeAction = args['takeAction'] | |
except KeyError: | |
print(errorMessage) | |
takeAction = takeActionDefault | |
if takeAction <= 0.0: | |
print 'The time to take action must be bigger than 0.0.' | |
sys.exit(1) | |
takeActionTime = takeAction | |
if takeActionTime < checkForNewAlarmsInterval: | |
print 'The time after which action is taken for not acknowledged alarms is smaller than the interval for checking' \ | |
'for new alarms: ' + str(takeActionTime) + ' < ' + str(checkForNewAlarmsInterval) + \ | |
'The take action time should be bigger or equal to the checking for new alarms time.Exiting now...' | |
sys.exit(1) | |
errorMessage = "Either (or both) information about alarm time periods or e-mails is missing. Please set them in " \ | |
"the settings file." | |
try: | |
if not settings['WC_ALARMS_ALERT_PERIODS'] or not settings['WC_ALARMS_ALERT_EMAILS']: | |
print(errorMessage) | |
sys.exit(1) | |
except KeyError: | |
print(errorMessage) | |
sys.exit(1) | |
# Setting two further "defaults": | |
numberOfReminders = 2 | |
loopThroughHierarchiesInterval = 60.0 # unit: minutes | |
#################################################################################################################### | |
### Connect to the webCTRL server | |
#################################################################################################################### | |
try: | |
client = suds.client.Client(wsdlFile, username=username, password=password) | |
except AttributeError: | |
print 'Error: Incorrect username and/or password' | |
except xml.sax._exceptions.SAXParseException: | |
print 'Error: Incorrect/Misspelled WSDL file. It should be: http(s)://URL?wsdl' | |
sys.exit(1) | |
except: | |
print("Unexpected error:", sys.exc_info()[0]) | |
print('Perhaps your URL to the WSDL file, ' + wsdlFile + ', is not correct.') | |
sys.exit(1) | |
#################################################################################################################### | |
### Get the alarms and process them for as long as this script runs | |
#################################################################################################################### | |
alarmsActive = [] # active alarms waiting to be checked/acknowledged and, eventually, switched off | |
alarmsChecked = [] # alarms that are already acknowledged or switched off | |
# As long as this script runs get a report about all current alarms and their status every X seconds. The alarms are | |
# managed in objects that will be deleted once the status of an alarm has switched to "Normal". It will be checked | |
# if an alarm is handled - at least acknowledged - in a certain amount of time and, if not, consequences follow as | |
# e.g. e-mail or SMS to a given address or telephone number. | |
while True: | |
# Get a report about all current alarms in the network or in parts of the network (depending on the node setting) | |
try: | |
report = client.service.runReport(node, '~alarms', 'csv') | |
except suds.WebFault as fault: | |
print fault | |
sys.exit(1) | |
lines = report.split("\n") | |
lines = lines[1:] | |
# Comment above two lines and uncomment below two lines in order to test the program | |
#file = open("testAlarms.txt", "r") | |
#lines = file.read().split("\n") | |
# Go through the report and create alarm objects distinguishing by their current alarm status. | |
# Start looping from the last line in order to process the oldest alarms first. | |
for line in reversed(lines): | |
if 'Aktueller Status' in line: | |
status = line[18:] | |
indexOfQuote = status.index('"') | |
status = status[:indexOfQuote] | |
elif 'Kategorie' in line: | |
kategorie = line[11:] | |
elif 'Besttigt' in line: | |
bestaetigtVon = line[10:] | |
elif 'Quelle' in line: | |
quelle = line[8:] | |
elif '","' in line: | |
parts = line.split(",") | |
dateAndTime = parts[0].replace('"', '').split(" ") | |
standort = parts[1].replace('"', '') | |
message = parts[2].replace('"', '') | |
date = dateAndTime[0] | |
parts = date.split(".") | |
day = int(parts[0]) | |
month = int(parts[1]) | |
year = int(parts[2]) | |
time = dateAndTime[1] | |
parts = time.split(":") | |
hours = int(parts[0]) | |
minutes = int(parts[1]) | |
seconds = int(parts[2]) | |
date = datetime.datetime(year, month, day, hours, minutes, seconds) | |
# Create the alarm object and add the unchecked/unsolved alarms to a list for observation | |
if status == 'Normal': | |
# Before appending the current alarm to the list of alarms check if this alarm is already contained | |
# in the list: | |
alarmAlreadyKnown = False | |
for a in alarmsChecked: | |
if a.quelle == quelle and a.date == date: | |
# This alarm has already been checked; move on to the next alarm | |
alarmAlreadyKnown = True | |
if not alarmAlreadyKnown: | |
# Before appending the checked/solved alarm to the list, first check if this alarm is already | |
# the check/solution to a prior alarm with status e.g. 'unnormal': | |
removedAlarm = False | |
for a in alarmsActive: | |
if a.quelle == quelle and a.date == date: | |
# Remove alarm from running and do not append to checked | |
removedAlarm = True | |
alarmsActive.remove(a) | |
if verbosity: print('Removing alarm ' + str(a.date) + ' ' + a.quelle + ' from alarmsActive') | |
if not removedAlarm: | |
# Ignore the alarms that have a 'Normal' status without another matching alarm with 'Unnormal' | |
# status because it's already solved | |
pass | |
else: | |
# Alarms with "Unnormal" status; before appending the current alarm to the list of alarms check if | |
# this alarm is already contained in the list: | |
alarmAlreadyKnown = False | |
for a in alarmsActive: | |
if a.quelle == quelle and a.date == date: | |
# This alarm is already known; move on to the next alarm | |
alarmAlreadyKnown = True | |
# Observe the age of the alarm, i.e, increase the age by the time interval in which new alarms are caught | |
a.age += checkForNewAlarmsInterval | |
a.stillActive = True | |
if not alarmAlreadyKnown: | |
if verbosity: print('Appending alarm ' + str(date) + ' ' + quelle + ' to alarmsActive') | |
age = 0.0 | |
a = alarm(date, age, standort, message, quelle, bestaetigtVon, kategorie, status) | |
a.stillActive = True | |
alarmsActive.append(a) | |
if verbosity: | |
if alarmsChecked: | |
for a in alarmsChecked: | |
print(a.date, a.standort, a.message, a.quelle, a.bestaetigtVon, a.kategorie, a.status) | |
else: | |
print('No alarms in list alarmsChecked') | |
# Handling of active alarms, e.g., send a notification e-mail depending on the age of an alarm | |
if alarmsActive: | |
for a in alarmsActive: | |
# Remove "Zombi alarms", that is, alarms that are still marked as active but do not appear in the retrieved | |
# alarms list anymore. So, these alarms were acknowledged/solved AND deleted between two alarms retrievals. | |
if not a.stillActive: | |
if verbosity: | |
print("This alarm seems to have been solved and deleted so we're removing it from the list of active alarms:") | |
print(a.date, a.standort, a.message, a.quelle, a.bestaetigtVon, a.kategorie, a.status) | |
alarmsActive.remove(a) | |
continue | |
if verbosity: | |
print(a.date, a.standort, a.message, a.quelle, a.bestaetigtVon, a.kategorie, a.status) | |
print 'Age: ' + str(a.age) | |
print 'a.reminderNotification= ' + str(a.reminderNotification) | |
print 'a.firstNotification= ' + str(a.firstNotification) | |
# Initialisations in case there hasn't been any e-mail yet about this alarm | |
if not a.firstNotification: | |
a.firstNotification = True | |
a.reminderNotification = 0 | |
# Check the age of the still active alarms and take action in case of, for example, the age is greater than e.g. 20 min | |
sourceMailAddress = 'ppb@fhi-berlin.mpg.de' | |
currentHour = datetime.datetime.now().hour | |
allMailAddressesForCurrentPeriod = getMailAddresses(currentHour, settings['WC_ALARMS_ALERT_EMAILS'], settings['WC_ALARMS_ALERT_PERIODS']) | |
if allMailAddressesForCurrentPeriod.startswith("ERROR"): | |
print(allMailAddressesForCurrentPeriod) | |
print("Exiting now...") | |
sys.exit(1) | |
addresses = allMailAddressesForCurrentPeriod.split("#") | |
# Select the correct mail addresses depending on the hierarchy: | |
if a.reminderNotification < numberOfReminders+1: | |
if a.age >= takeActionTime and (a.age % takeActionTime == 0): | |
mailAddresses = addresses[a.notificationHierarchy] | |
if verbosity: print 'Sending an e-mail to ' + mailAddresses | |
sendEmails(sourceMailAddress, mailAddresses, a.date, a.standort, a.message, a.quelle) | |
else: | |
if (a.age - takeActionTime * numberOfReminders) % loopThroughHierarchiesInterval == 0: | |
a.notificationHierarchy += 1 | |
try: | |
mailAddresses = addresses[a.notificationHierarchy] | |
except IndexError: | |
print("Index Error! a.notificationHierarchy = " + str(a.notificationHierarchy)) | |
# get the addresses of the highest hierarchy no matter what the index is at the moment | |
mailAddresses = addresses[-1] | |
if verbosity: print("Sending an e-mail at age = " + str(a.age) + " to: " + mailAddresses) | |
sendEmails(sourceMailAddress, mailAddresses, a.date, a.standort, a.message, a.quelle) | |
if a.firstNotification and (a.age % takeActionTime == 0) and 0 <= a.reminderNotification < numberOfReminders + 1: | |
# Only send a small amount of reminder mails about an alarm because we don't want the recipient to get spamed | |
a.reminderNotification += 1 | |
for a in alarmsActive: | |
a.stillActive = False | |
else: | |
if verbosity: print('No alarms in list alarmsActive') | |
if verbosity: print('-----------------------------------------------------------------------------------------') | |
# sleep for n minutes | |
sleep(checkForNewAlarmsInterval*60) | |
# The argument parser is only called if this script is called as a script/executable (via command line) but not when | |
# imported by another script | |
if __name__ == '__main__': | |
if len(sys.argv) < 2: | |
print "You haven't specified any arguments. Use -h to get more details on how to use this command." | |
sys.exit(1) | |
parser = argparse.ArgumentParser() | |
parser._action_groups.pop() | |
required = parser.add_argument_group('required arguments') | |
optional = parser.add_argument_group('optional arguments (can also be specified via SETTINGS file)') | |
required.add_argument('--password', '-p', type=str, default=None, help='Password for the login to the WebCTRL server') | |
required.add_argument('--settingsFile', '-f', type=str, default=None, help='File name of settings containing WC_* variables') | |
required.add_argument('--username', '-u', type=str, default=None, help='Username for the login to the WebCTRL server') | |
optional.add_argument('--verbosity', '-v', action='store_true', default=None, help="If '-v' is selected you'll get verbose output") | |
optional.add_argument('--url', '-url', type=str, default=None, help="URL of the webctrl server like 'http(s)://webctrl.de'.") | |
optional.add_argument('--node', '-n', type=str, default=None, | |
help="Path to the point or node for which you want to retrieve all alarms. If '-n /trees/geographic' you'll get all alarms in the network.") | |
optional.add_argument('-interval', '-i', type=int, default=None, help="Interval in MINUTES in which the script checks for new alarms") | |
optional.add_argument('-takeAction', '-a', type=int, default=None, help="Time in MINUTES after which an action is taken if an alarm still hasn't been acknowledged") | |
args = parser.parse_args() | |
# Get the password if it hasn't been passed as argument | |
if args.password is None or not args.password: | |
args.password = getpass.getpass('No password specified via -p. Please enter your WebCTRL login password now: ') | |
# Convert the argparse.Namespace to a dictionary via vars(args) | |
main(vars(args)) | |
sys.exit(0) |