diff --git a/bot.py b/bot.py index acc5464..71c5864 100644 --- a/bot.py +++ b/bot.py @@ -1,5 +1,6 @@ import slixmpp import logging +import random from systemd.journal import JournalHandler from hintTables import annecdotetable, topictable @@ -9,7 +10,6 @@ from commands import commandtable # Logging log = logging.getLogger(__name__) log.addHandler(JournalHandler()) -log.setLevel(logging.INFO) class MUCBot(slixmpp.ClientXMPP): @@ -43,7 +43,7 @@ class MUCBot(slixmpp.ClientXMPP): # any presences you send yourself. To limit event handling # to a single room, use the events muc::room@server::presence, # muc::room@server::got_online, or muc::room@server::got_offline. - self.add_event_handler("muc::%s::got_online" % self.room, + self.add_event_handler("muc::%s::got_online" % self.rooms[0], self.muc_online) @@ -135,7 +135,7 @@ class MUCBot(slixmpp.ClientXMPP): the user's nickname """ - if presence['muc']['nick'] in self.datastore.maintainerData: + if presence['muc']['nick'] in self.datastore.knownMaintainers: self.send_message(mto=presence['from'].bare, mbody="Salut %s, vos services ont produit des " + "alertes en votre absence !\nUtilisez la commande"+ diff --git a/commands.py b/commands.py index 295f1fe..974079a 100644 --- a/commands.py +++ b/commands.py @@ -2,29 +2,30 @@ import pickle import logging from systemd.journal import JournalHandler +from data import HostData + # Logging log = logging.getLogger(__name__) log.addHandler(JournalHandler()) -log.setLevel(logging.INFO) # Isengard commands -def cmdping(owners, nick, text, sbuf): +def cmdping(owners, nick, text, store): """ Ping command. """ return "pong !" -def cmdhelp(owners, nick, text, sbuf): +def cmdhelp(owners, nick, text, store): """ Ping command. """ global commandtable - msg = "Je suis Isengard, le bot de supervision de ce salon.\n" + msg = "je suis Isengard, le bot de supervision de ce salon.\n" msg += "\n" msg += "Voici la liste de mes commandes disponibles : \n" @@ -33,7 +34,7 @@ def cmdhelp(owners, nick, text, sbuf): return msg -def cmdmainteneur(owners, nick, text, sbuf): +def cmdmainteneur(owners, nick, text, store): """ Change maintainer for an host """ @@ -49,31 +50,29 @@ def cmdmainteneur(owners, nick, text, sbuf): host = splittedtext[1] return "le responsable de cette machine est " \ - + sbuf.serviceData[host]["maintainer"] + + store.knownHosts[host]["maintainer"] if len(splittedtext) == 3: host = splittedtext[1] maintainer = splittedtext[2] - sbuf.serviceData[host] = {} - sbuf.serviceData[host]["destmuc"] = None - sbuf.serviceData[host]["status_state"] = None - sbuf.serviceData[host]["status_type"] = None - sbuf.serviceData[host]["text"] = None - sbuf.serviceData[host]["raw"] = None - sbuf.serviceData[host]["needUpdate"] = False - sbuf.serviceData[host]["maintainer"] = maintainer - - return "le responsable est à présent " \ - + sbuf.serviceData[host]["maintainer"] + if not host in store.knownHosts: + store.knownHosts[host] = HostData(host) + + store.knownHosts[host].maintainer = maintainer + return "le responsable est à présent " + maintainer + + except KeyError as e: + log.error(repr(e)) + return "machine inconnue (tout le monde est son mainteneur)" except Exception as e: - print(repr(e)) - return "Erreur à l'exécution" + log.error(repr(e)) + return "erreur à l'exécution" return "Syntaxe invalide" -def cmdsave(owners, nick, text, sbuf): +def cmdsave(owners, nick, text, store): """ Save """ @@ -83,14 +82,14 @@ def cmdsave(owners, nick, text, sbuf): try: with open('current_buffer', 'wb') as current_buffer_file: - pickle.dump(sbuf.serviceData, current_buffer_file) + pickle.dump(store.knownHosts, current_buffer_file) return "OK" except Exception as e: - print(repr(e)) - return "Erreur à l'exécution" + log.error(repr(e)) + return "erreur à l'exécution" -def cmdload(owners, nick, text, sbuf): +def cmdload(owners, nick, text, store): """ Save """ @@ -100,29 +99,34 @@ def cmdload(owners, nick, text, sbuf): try: with open('current_buffer', 'rb') as current_buffer_file: - sbuf.serviceData = pickle.load(current_buffer_file) + store.knownHosts = pickle.load(current_buffer_file) return "OK" except Exception as e: - print(repr(e)) - return "Erreur à l'exécution" + log.error(repr(e)) + return "erreur à l'exécution" -def cmdhist(owners, nick, text, sbuf): +def cmdhist(owners, nick, text, store): """ Check history for a maintainer """ - if not nick in sbuf.maintainerData or len(sbuf.maintainerData[nick]) == 0: + if not nick in store.knownMaintainers or len(store.knownMaintainers[nick]) == 0: return "pas d'historique disponible pour vous." - msg = "voici historique pour le mainteneur %s (vous) :\n" % nick - for host in sbuf.maintainerData[nick]: + msg = "voici les cinq derniers évènements pour chaque hôte notifié:\n" + for host in store.knownMaintainers[nick]: msg += "\nHôte %s:" % host - for serviceline in sbuf.maintainerData[nick][host]: + count = 0 + for serviceline in store.knownMaintainers[nick][host]: msg += "- %s" % serviceline + "\n" + count += 1 + if count >= 5: + break - sbuf.maintainerData[nick] = {} + del store.knownMaintainers[nick] + store.knownMaintainers[nick] = {} return msg # Commands diff --git a/current_buffer b/current_buffer new file mode 100644 index 0000000..163b0cc Binary files /dev/null and b/current_buffer differ diff --git a/data.py b/data.py index e7ada02..c033627 100644 --- a/data.py +++ b/data.py @@ -7,7 +7,7 @@ from systemd.journal import JournalHandler # Logging log = logging.getLogger(__name__) log.addHandler(JournalHandler()) -log.setLevel(logging.INFO) + class HostData: """ @@ -17,21 +17,19 @@ class HostData: def __init__(self, name): self.name = name - # Concerning services - self.serviceLastType = "" - self.serviceLastStatus = "" - self.serviceLastText = "" - # Concerning host - self.lastType = "" - self.lastStatus = "" + self.type = "" + self.status = "OK" + + # Concerning services + self.statuses = {} + self.types = {} # Tools - self.linkedMUC = "" - self.notifCount = 0 - self.needUpdate = False + self.counts = {"CRITICAL":0, "WARNING":0, "OK":0} self.maintainer = "Tout le monde" + class DataStore: def __init__(self, linkedBot): @@ -43,29 +41,172 @@ class DataStore: self.linkedBot = linkedBot def push(self, msg): - - # TYPE|HOST/SERVICE|STATE|OUTPUT|SENDER|COMMENT + """ + Process messages like TYPE|HOST/SERVICE|STATE|OUTPUT|SENDER|COMMENT + """ # Get current time curtime = datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S") - # Get all params - destmuc, status_type, location, status_state, text, sender, comment = msg.split("|") - print("Dest: %s, Msg: %s" % (destmuc, msg)) - print("Status: %s" % (status_type + " (" + status_state + ")")) - - # check if message is about a service or host - if len(location.split("/")) > 1: + destmuc, type, location, status, text, sender, comment = msg.split("|") + # Check if message is about a service or host + try: host, service = location.split("/") + except ValueError: + host = location.split("/")[0] + service = '' + + # Create raw text from notification + raw = "%s [%s/%s]: %s (%s %s)" % (curtime, type, status, text, + sender, comment) + + cur = None + + log.info("Datastore received: %s" % msg) + log.info("Datastore understood: %s" % raw) + + # Look for host + if host in self.knownHosts: + cur = self.knownHosts[host] + + # It's a service + if service != "": + # is it known ? + if service in cur.statuses: + # does the status change ? + if cur.statuses[service] != status: + # update + if not cur.statuses[service] in cur.counts: + cur.counts[status] = 0 + cur.counts[cur.statuses[service]] -= 1 + if not status in cur.counts: + cur.counts[status] = 0 + cur.counts[status] += 1 + cur.statuses[service] = status + cur.types[service] = status + else: + # create status entry + cur.statuses[service] = status + if not status in cur.counts: + cur.counts[status] = 0 + cur.counts[status] += 1 + + # It's not a service (so general) + else: + # does the status change ? + if cur.status != status: + cur.status = status + cur.type = type + + # Host is not known else: - host = location - service = False - print("Host: %s, Service: %s" % (host, service)) - - print("Text: %s" % (text)) - - + # create host + self.knownHosts[host] = HostData(host) + cur = self.knownHosts[host] + # It's a service + if service != "": + # create status entry + cur.statuses[service] = status + if not status in cur.counts: + cur.counts[status] = 0 + cur.counts[status] += 1 + + # It's not a service (so general) + else: + # create status entry + cur.status = status + cur.type = type + + # Update history + if not cur.maintainer in self.knownMaintainers: + self.knownMaintainers[cur.maintainer] = {} + + if not host in self.knownMaintainers[cur.maintainer]: + self.knownMaintainers[cur.maintainer][host] = [] + + self.knownMaintainers[cur.maintainer][host].append(raw) + + # Is there only one service or more problems for this host? + problemCount = 0 + for cstatus in cur.counts: + if not "OK" in cstatus: + problemCount += cur.counts[cstatus] + + # If this notification is a problem + if not "OK" in status: + # General problem + if not "OK" in cur.status and problemCount == 0: + message = "je détecte un problème général (%s)" \ + " sur %s (%s)" % (status, host, text) + # send notification + log.info("Sending to %s: %s" % (destmuc, message)) + self.linkedBot.push(destmuc, cur.maintainer+", "+message) + + # Only one service has a problem + elif service and problemCount == 1: + message = "je détecte un problème (%s) sur le service %s de" \ + " la machine %s" \ + " (%s)" % (status, service, host, text) + # send notification + log.info("Sending to %s: %s" % (destmuc, message)) + self.linkedBot.push(destmuc, cur.maintainer+", "+message) + + # Multiple problems + else: + message = "je détecte de multiples problèmes " \ + "sur la machine %s\n" % (host) + # create recap from statuses that are not OK + for cstatus in cur.counts: + if not "OK" in cstatus: + message += "%s %s(s), " % \ + (str(cur.counts[cstatus]), cstatus) + message = message[:-2] + # send notification + log.info("Sending to %s: %s" % (destmuc, message)) + self.linkedBot.push(destmuc, cur.maintainer+", "+message) + + # We have a recovery + else: + + # General problem + if not service and problemCount == 0: + message = "fin d'alerte générale sur" \ + " sur %s (%s)" % (host, text) + # send notification + log.info("Sending to %s: %s" % (destmuc, message)) + self.linkedBot.push(destmuc, cur.maintainer+", "+message) + + # Only one service has a problem + elif service and problemCount == 0: + message = "résolution du problème sur le service %s de" \ + " la machine %s" \ + " (%s)\n" % (service, host, text) + # create recap from statuses that are not OK + for cstatus in cur.counts: + message += "%s %s(s), " % \ + (str(cur.counts[cstatus]), cstatus) + message = message[:-2] + # send notification + log.info("Sending to %s: %s" % (destmuc, message)) + self.linkedBot.push(destmuc, cur.maintainer+", "+message) + + # Resolution but multiple problems + else: + message = "résolution d'alertes en cours " \ + "sur la machine %s\n" % (host) + # create recap from statuses + for cstatus in cur.counts: + message += "%s %s(s), " % \ + (str(cur.counts[cstatus]), cstatus) + message = message[:-2] + # send notification + log.info("Sending to %s: %s" % (destmuc, message)) + self.linkedBot.push(destmuc, cur.maintainer+", "+message) + + + log.info("Datastore known hosts: %s" % str(self.knownHosts)) + log.info("Datastore known maintainers: %s" % str(self.knownHosts)) diff --git a/localServer.py b/localServer.py index 04a763d..3ff7282 100644 --- a/localServer.py +++ b/localServer.py @@ -7,7 +7,6 @@ from systemd.journal import JournalHandler # Logging log = logging.getLogger(__name__) log.addHandler(JournalHandler()) -log.setLevel(logging.INFO) class LocalServer(threading.Thread): @@ -19,23 +18,23 @@ class LocalServer(threading.Thread): def run(self): sockfd = socket.socket() - print("Socket successfully created") + log.info("Socket successfully created") port = 12346 # Reserve a port on your computer...in our case it is 12345, but it can be anything sockfd.bind(('127.0.0.1', port)) - print("Socket binded to %s" %(port)) + log.info("Socket binded to %s" %(port)) sockfd.listen(5) # Put the socket into listening mode - print("Socket is listening") + log.info("Socket is listening") while not self.pleaseStop: client, addr = sockfd.accept() # Establish connection with client - print('Got connection from', addr) + log.debug('Got connection from', addr) rcvStr = client.recv(1024).decode() - print(rcvStr) + log.debug(rcvStr) if rcvStr != '': self.sharedBuffer.push(rcvStr) client.send(b'') # Send a message to the client client.close() - print("Socket is listening") - print("Socket is closed\nExiting") + log.debug("Socket is listening") + log.info("Socket is closed\nExiting") diff --git a/main.py b/main.py index a1a6c0a..4f12945 100755 --- a/main.py +++ b/main.py @@ -14,12 +14,6 @@ from bot import MUCBot from data import DataStore from localServer import LocalServer - -# Logging -log = logging.getLogger(__name__) -log.addHandler(JournalHandler()) -log.setLevel(logging.INFO) - if __name__ == '__main__': def cleanExit(): @@ -58,8 +52,8 @@ if __name__ == '__main__': help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") - parser.add_argument("-r", "--room", dest="room", - help="MUC room to join") + parser.add_argument("-r", "--rooms", dest="rooms", + help="MUC rooms to join separated by comma") parser.add_argument("-n", "--nick", dest="nick", help="MUC nickname") @@ -68,11 +62,29 @@ if __name__ == '__main__': # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') + log = logging.getLogger(__name__) + log.addHandler(JournalHandler()) + # Setup the MUCBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. - xmpp = MUCBot(args.jid, args.password, args.room, args.nick) + + jid = args.jid + password = args.password + rooms = args.rooms + nick = args.nick + + if not jid: + jid = "isengard@a-lec.org" + if not password: + password = "iLw62WF3kfvt4j" + if not rooms: + rooms = "tests-isengard@salons.a-lec.org" + if not nick: + nick = "Isengard" + + xmpp = MUCBot(jid, password, rooms.split(","), nick) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0045') # Multi-User Chat xmpp.register_plugin('xep_0199') # XMPP Ping