Logo Search packages:      
Sourcecode: nagstamon version File versions  Download package

nagstamonObjects.py

#!/usr/bin/python
# encoding: utf-8

import urllib2
import sys
import socket
import gc
import copy

try:
    import lxml.etree, lxml.objectify
except Exception, err:
    print
    print err
    print
    print "Could not load lxml.etree, lxml.objectify and lxml.html.clean, maybe you need to install python lxml."
    print
    sys.exit()
# fedora 8 and maybe others use lxml 2 which is more careful and offers more modules
# but which also makes necessary to clean Nagios html output
# if not available should be ok because not needed
try:
    import lxml.html.clean
except:
    pass
    
import nagstamonActions

          
00030 class NagiosServer(object):
    """
        object of Nagios server - when nagstamon will be able to poll various servers this
        will be useful   
    """
    
    def __init__(self, **kwds):
        # add all keywords to object, every mode searchs inside for its favorite arguments/keywords
        for k in kwds: self.__dict__[k] = kwds[k]

        self.name = ""
        self.nagios_url = ""
        self.nagios_cgi_url = ""
        self.username = ""
        self.password = ""
        self.use_proxy = False
        self.use_proxy_from_os = False
        self.proxy_address = ""
        self.proxy_username = ""
        self.proxy_password = ""        
        self.hosts = dict()
        self.hosts_in_maintenance = list()
        self.hosts_acknowledged = list()
        self.new_hosts = dict()
        self.new_hosts_in_maintenance = list()
        self.new_hosts_acknowledged = list()
        self.thread = ""
        self.isChecking = False
        self.debug = False
        self.CheckingForNewVersion = False
        self.WorstStatus = "OK"
        self.States = ["OK", "UNKNOWN", "WARNING", "CRITICAL", "UNREACHABLE", "DOWN"]
        self.nagitems_filtered_list = list()
        self.downs = 0
        self.unreachables = 0
        self.unknowns = 0
        self.criticals = 0
        self.warnings = 0
        self.status = ""

    
00071     def __del__(self):
        """
        hopefully a __del__() method may make this object better collectable for gc
        """
        del(self)
        
    
00078     def GetStatus(self):
        """
        get nagios status information from nagcgiurl and give it back
        as dictionary
        """

        # set checking flag to be sure only one thread cares about this server
        self.isChecking = True
        
        # check if server is enabled, if not, do not get any status
        if str(self.conf.servers[self.name].enabled) == "False":
            self.WorstStatus = "OK"
            self.nagitems_filtered = {"services":{"CRITICAL":[], "WARNING":[], "UNKNOWN":[]}, "hosts":{"DOWN":[], "UNREACHABLE":[]}}
            self.isChecking = False          
            return True

        # create filters like described in
        # http://www.nagios-wiki.de/nagios/tips/host-_und_serviceproperties_fuer_status.cgi?s=servicestatustypes
        # hoststatus
        hoststatustypes = 12
        if str(self.conf.filter_all_down_hosts) == "True":
            hoststatustypes = hoststatustypes - 4
        if str(self.conf.filter_all_unreachable_hosts) == "True":
            hoststatustypes = hoststatustypes - 8
        # servicestatus
        servicestatustypes = 28
        if str(self.conf.filter_all_unknown_services) == "True":
            servicestatustypes = servicestatustypes - 8
        if str(self.conf.filter_all_warning_services) == "True":
            servicestatustypes = servicestatustypes - 4
        if str(self.conf.filter_all_critical_services) == "True":
            servicestatustypes = servicestatustypes - 16
        # serviceprops & hostprops both have the same values for the same states so I
        # group them together
        hostserviceprops = 0
        if str(self.conf.filter_acknowledged_hosts_services) == "True":
            hostserviceprops = hostserviceprops + 8
        if str(self.conf.filter_hosts_services_disabled_notifications) == "True":
            hostserviceprops = hostserviceprops + 8192
        if str(self.conf.filter_hosts_services_disabled_checks) == "True":
            hostserviceprops = hostserviceprops + 32
        if str(self.conf.filter_hosts_services_maintenance) == "True":
            hostserviceprops = hostserviceprops + 2
        
        # create Nagios items dictionary with to lists for services and hosts
        # every list will contain a dictionary for every failed service/host
        nagitems = {"services":[], "hosts":[]}

        # services (unknown, warning or critical?)
        nagcgiurl_services = self.nagios_cgi_url + "/status.cgi?host=all&servicestatustypes=" + str(servicestatustypes) + "&serviceprops=" + str(hostserviceprops)
        # hosts (up or down or unreachable)
        nagcgiurl_hosts = self.nagios_cgi_url + "/status.cgi?hostgroup=all&style=hostdetail&hoststatustypes=" + str(hoststatustypes) + "&hostprops=" + str(hostserviceprops)
        # fetching hosts in downtime and acknowledged hosts at once is not possible because these 
        # properties get added and nagios display ONLY hosts that have BOTH states
        # hosts that are in scheduled downtime, we will later omit services on those hosts
        # hostproperty 1 = HOST_SCHEDULED_DOWNTIME 
        nagcgiurl_hosts_in_maintenance = self.nagios_cgi_url + "/status.cgi?hostgroup=all&style=hostdetail&hostprops=1"
        # hosts that are acknowledged, we will later omit services on those hosts
        # hostproperty 4 = HOST_STATE_ACKNOWLEDGED 
        nagcgiurl_hosts_acknowledged = self.nagios_cgi_url + "/status.cgi?hostgroup=all&style=hostdetail&hostprops=4"

        # hosts - mostly the down ones
        # unfortunately the hosts status page has a different structure so
        # hosts must be analyzed separately
        try:
            htobj = self.FetchURL(nagcgiurl_hosts, giveback="dict")
            # workaround for Nagios < 2.7 which has an <EMBED> in its output
            # do a copy of a part of htobj into table to be able to delete htobj
            try:
                table = copy.copy(htobj.body.div.table)
            except:
                table = copy.copy(htobj.body.embed.div.table)
            
            # do some cleanup    
            del htobj

            for i in range(1, len(table.tr)):
                try:
                    # ignore empty <tr> rows
                    if not table.tr[i].countchildren() == 1:
                        n = {}
                        # host
                        try:
                            n["host"] = str(table.tr[i].td[0].table.tr.td.table.tr.td.a.text)
                        except:
                            n["host"] = str(nagitems[len(nagitems)-1]["host"])
                        # status
                        n["status"] = str(table.tr[i].td[1].text)
                        # last_check
                        n["last_check"] = str(table.tr[i].td[2].text)
                        # duration
                        n["duration"] = str(table.tr[i].td[3].text)
                        # status_information
                        n["status_information"] = str(table.tr[i].td[4].text)
                        # attempts are not shown in case of hosts so it defaults to "N/A"
                        n["attempt"] = "N/A"
                        
                        # add dictionary full of information about this host item to nagitems
                        nagitems["hosts"].append(n)
                        # after collection data in nagitems create objects from its informations
                        # host objects contain service objects
                        if not self.new_hosts.has_key(n["host"]):
                            self.new_hosts[n["host"]] = NagiosHost()
                            self.new_hosts[n["host"]].name = n["host"]
                            self.new_hosts[n["host"]].status = n["status"]
                            self.new_hosts[n["host"]].last_check = n["last_check"]
                            self.new_hosts[n["host"]].duration = n["duration"]
                            self.new_hosts[n["host"]].attempt = n["attempt"]
                            self.new_hosts[n["host"]].status_information= n["status_information"]
                except:
                    pass
                
            # do some cleanup
            del table
            
        except:
            # set checking flag back to False
            self.isChecking = False
            return "ERROR"

        # services
        try:
            htobj = self.FetchURL(nagcgiurl_services, giveback="dict")

            for i in range(1, len(htobj.body.table[2].tr)):
                try:
                    # ignore empty <tr> rows - there are a lot of them - a Nagios bug? 
                    if not htobj.body.table[2].tr[i].countchildren() == 1:
                        n = {}
                        # host
                        # the resulting table of Nagios status.cgi table omits the
                        # hostname of a failing service if there are more than one
                        # so if the hostname is empty the nagios status item should get
                        # its hostname from the previuos item - one reason to keep "nagitems"
                        try:
                            n["host"] = str(htobj.body.table[2].tr[i].td[0].table.tr.td.table.tr.td.a.text)
                        except:
                            n["host"] = str(nagitems["services"][len(nagitems["services"])-1]["host"])
                        # service
                        n["service"] = str(htobj.body.table[2].tr[i].td[1].table.tr.td.table.tr.td.a.text)
                        # status
                        n["status"] = str(htobj.body.table[2].tr[i].td[2].text)
                        # last_check
                        n["last_check"] = str(htobj.body.table[2].tr[i].td[3].text)
                        # duration
                        n["duration"] = str(htobj.body.table[2].tr[i].td[4].text)
                        # attempt
                        n["attempt"] = str(htobj.body.table[2].tr[i].td[5].text)
                        # status_information
                        n["status_information"] = str(htobj.body.table[2].tr[i].td[6].text)
                        # add dictionary full of information about this service item to nagitems - only if service
                        nagitems["services"].append(n)
                        
                        # after collection data in nagitems create objects of its informations
                        # host objects contain service objects
                        if not self.new_hosts.has_key(n["host"]):
                            self.new_hosts[n["host"]] = NagiosHost()
                            self.new_hosts[n["host"]].name = n["host"]
                            self.new_hosts[n["host"]].status = "OK"
                        # if a service does not exist create its object
                        if not self.new_hosts[n["host"]].services.has_key(n["service"]):
                            self.new_hosts[n["host"]].services[n["service"]] = NagiosService()
                            self.new_hosts[n["host"]].services[n["service"]].host = n["host"]
                            self.new_hosts[n["host"]].services[n["service"]].name = n["service"]
                            self.new_hosts[n["host"]].services[n["service"]].status = n["status"]
                            self.new_hosts[n["host"]].services[n["service"]].last_check = n["last_check"]
                            self.new_hosts[n["host"]].services[n["service"]].duration = n["duration"]
                            self.new_hosts[n["host"]].services[n["service"]].attempt = n["attempt"]
                            self.new_hosts[n["host"]].services[n["service"]].status_information = n["status_information"]
                except:
                    pass
                                
            # do some cleanup
            del htobj
            
        except:
            # set checking flag back to False
            self.isChecking = False
            return "ERROR"
       
         # hosts which are in scheduled downtime
        try:
            htobj = self.FetchURL(nagcgiurl_hosts_in_maintenance, giveback="dict")
           
            # workaround for Nagios < 2.7 which has an <EMBED> in its output
            try:
                table = copy.copy(htobj.body.div.table)
            except:
                table = copy.copy(htobj.body.embed.div.table)
            
            # do some cleanup    
            del htobj

            for i in range(1, len(table.tr)):
                try:
                    # ignore empty <tr> rows
                    if not table.tr[i].countchildren() == 1:
                        # host
                        try:
                            self.new_hosts_in_maintenance.append(str(table.tr[i].td[0].table.tr.td.table.tr.td.a.text))
                            # get real status of maintained host
                            if self.new_hosts.has_key(self.new_hosts_acknowledged[-1]):
                                self.new_hosts[self.new_hosts_acknowledged[-1]].status = str(table.tr[i].td[1].text)
                        except:
                            pass
                except:
                    pass

            # do some cleanup
            del table
        
        except:
            # set checking flag back to False
            self.isChecking = False
            return "ERROR"
        
        # hosts which are acknowledged
        try:
            htobj = self.FetchURL(nagcgiurl_hosts_acknowledged, giveback="dict")
            # workaround for Nagios < 2.7 which has an <EMBED> in its output
            try:
                table = copy.copy(htobj.body.div.table)
            except:
                table = copy.copy(htobj.body.embed.div.table)
            
            # do some cleanup    
            del htobj               

            for i in range(1, len(table.tr)):
                try:
                    # ignore empty <tr> rows
                    if not table.tr[i].countchildren() == 1:
                        # host
                        try:
                            self.new_hosts_acknowledged.append(str(table.tr[i].td[0].table.tr.td.table.tr.td.a.text))
                            # get real status of acknowledged host
                            if self.new_hosts.has_key(self.new_hosts_acknowledged[-1]):
                                self.new_hosts[self.new_hosts_acknowledged[-1]].status = str(table.tr[i].td[1].text)
                        except:
                            pass
                except:
                    pass

            # do some cleanup
            del table

        except:
            # set checking flag back to False
            self.isChecking = False
            return "ERROR"

        # this part has been before in GUI.RefreshDisplay() - wrong place 
        self.nagitems_filtered = {"services":{"CRITICAL":[], "WARNING":[], "UNKNOWN":[]}, "hosts":{"DOWN":[], "UNREACHABLE":[]}}
        
        # initialize counts for varios service/hosts states
        # count them with every miserable host/service respective to their meaning
        self.downs = 0
        self.unreachables = 0
        self.unknowns = 0
        self.criticals = 0
        self.warnings = 0

        for host in self.new_hosts.values():
            # filtering out hosts, sorting by severity
            if host.status == "DOWN" and nagstamonActions.HostIsFilteredOutByRE(host.name, self.conf) == False\
            and (not (host.name in self.new_hosts_in_maintenance and \
            str(self.conf.filter_hosts_services_maintenance) == "True") and \
            not (host.name in self.new_hosts_acknowledged and \
            str(self.conf.filter_acknowledged_hosts_services) == "True")): 
                self.nagitems_filtered["hosts"]["DOWN"].append(host)
                self.downs += 1
            if host.status == "UNREACHABLE" and nagstamonActions.HostIsFilteredOutByRE(host.name, self.conf) == False\
                and (not host.name in self.new_hosts_acknowledged and not host.name in self.new_hosts_in_maintenance): 
                self.nagitems_filtered["hosts"]["UNREACHABLE"].append(host)    
                self.unreachables += 1

            for service in host.services.values():
                # check hard/soft state, find out number of attempts and max attempts for
                # checking if soft state services should be shown
                real_attempt, max_attempt = service.attempt.split("/")
                # omit services on hosts in maintenance and acknowledged hosts
                if (not (host.name in self.new_hosts_in_maintenance and \
                str(self.conf.filter_hosts_services_maintenance) == "True") or \
                not (host.name in self.new_hosts_acknowledged and \
                str(self.conf.filter_acknowledged_hosts_services) == "True")) and \
                not (host.name in self.new_hosts_in_maintenance and\
                str(self.conf.filter_services_on_hosts_in_maintenance) == "True") and\
                not (real_attempt <> max_attempt and \
                str(self.conf.filter_services_in_soft_state) == "True") and \
                not (host.status == "DOWN" and \
                str(self.conf.filter_services_on_down_hosts) == "True") and \
                not (host.status == "UNREACHABLE" and \
                str(self.conf.filter_services_on_unreachable_hosts) == "True") and \
                nagstamonActions.HostIsFilteredOutByRE(host.name, self.conf) == False and \
                nagstamonActions.ServiceIsFilteredOutByRE(service.name, self.conf) == False:
                    # sort by severity
                    if service.status == "CRITICAL": 
                        self.nagitems_filtered["services"]["CRITICAL"].append(service)
                        self.criticals += 1
                    if service.status == "WARNING": 
                        self.nagitems_filtered["services"]["WARNING"].append(service)
                        self.warnings += 1
                    if service.status == "UNKNOWN": 
                        self.nagitems_filtered["services"]["UNKNOWN"].append(service)
                        self.unknowns += 1

        # find out if there has been some status change to notify user
        # compare sorted lists of filtered nagios items
        new_nagitems_filtered_list = []
        
        for i in self.nagitems_filtered["hosts"].values():
            for h in i:
                new_nagitems_filtered_list.append((h.name, h.status))   
            
        for i in self.nagitems_filtered["services"].values():
            for s in i:
                new_nagitems_filtered_list.append((s.host, s.name, s.status))  
                 
        # sort for better comparison
        new_nagitems_filtered_list.sort()

        # if both lists are identical there was no status change
        if (self.nagitems_filtered_list == new_nagitems_filtered_list):       
            self.WorstStatus = "OK"
        else:
            # if the new list is shorter than the first and there are no different hosts 
            # there one host/service must have been recovered, which is not worth a notification
            diff = []
            for i in new_nagitems_filtered_list:
                if not i in self.nagitems_filtered_list:
                    # collect differences
                    diff.append(i)
            if len(diff) == 0:
                self.WorstStatus = "OK"
            else:
                # if there are different hosts/services in list of new hosts there must be a notification
                # get list of states for comparison
                diff_states = []
                for d in diff:
                    diff_states.append(d[-1])
                # temporary worst state index   
                worst = 0
                for d in diff_states:
                    # only check worst state if it is valid
                    if d in self.States:
                        if self.States.index(d) > worst:
                            worst = self.States.index(d)
                            
                # final worst state is one of the predefined states
                self.WorstStatus = self.States[worst]
            
        # copy of listed nagitems for next comparison
        self.nagitems_filtered_list = copy.copy(new_nagitems_filtered_list)

        # do some cleanup
        self.hosts.clear()
        del self.hosts_acknowledged[::]
        del self.hosts_in_maintenance[::]

        # put new informations into respective dictionaries      
        self.hosts, self.hosts_acknowledged, self.hosts_in_maintenance = copy.copy(self.new_hosts), copy.copy(self.new_hosts_acknowledged), copy.copy(self.new_hosts_in_maintenance)
        
        # after all checks are done unset checking flag
        self.isChecking = False
        
        # do some cleanup
        del nagitems
        self.new_hosts.clear()
        del self.new_hosts_acknowledged[::]
        del self.new_hosts_in_maintenance[::]
        gc.collect()
        
        # return True if all worked well    
        return True
    
    
00454     def FetchURL(self, url, giveback="dict", cgi_data=None):
        """
        get content of given url, cgi_data only used if present
        giveback may be "dict", "html" or "none" 
        "dict" FetchURL gives back a dict full of miserable hosts/services,
        "html" it gives back pure HTML - useful for finding out IP or new version
        "none" it gives back pure nothing - useful if for example acknowledging a service
        existence of cgi_data forces urllib to use POST instead of GET requests
        """
        # using httppasswordmgrwithdefaultrealm because using password in plain
        # url like http://username:password@nagios-server causes trouble with
        # passwords containing special characters like "?"
        # see http://www.voidspace.org.uk/python/articles/authentication.shtml#doing-it-properly
        # attention: the example from above webseite is wrong, passman.add_password needs the 
        # WHOLE URL, with protocol!
        
        passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
        passman.add_password(None, url, self.username, self.password)
        auth_handler = urllib2.HTTPBasicAuthHandler(passman)

        # if something goes wrong with accessing the URL it can be caught
        try:
            # if there should be no proxy used use an empty proxy_handler - only necessary in Windows,
            # where IE proxy settings are used automatically if available
            # In UNIX $HTTP_PROXY will be used
            if str(self.use_proxy) == "False":
                proxy_handler = urllib2.ProxyHandler({})
                urlopener = urllib2.build_opener(auth_handler, proxy_handler)
            elif str(self.use_proxy) == "True":
                if str(self.use_proxy_from_os) == "True":
                    urlopener = urllib2.build_opener(auth_handler)
                else:
                    # if proxy from OS is not used there is to add a authenticated proxy handler
                    passman.add_password(None, self.proxy_address, self.proxy_username, self.proxy_password)
                    proxy_handler = urllib2.ProxyHandler({"http": self.proxy_address, "https": self.proxy_address})
                    proxy_auth_handler = urllib2.ProxyBasicAuthHandler(passman)
                    urlopener = urllib2.build_opener(proxy_handler, proxy_auth_handler, auth_handler)
            
            # create url opener
            urllib2.install_opener(urlopener)

            # use opener - if cgi_data is not empty urllib uses a POST request
            urlcontent = urllib2.urlopen(url, cgi_data)
            
            # give back pure HTML in case giveback is "html"
            if giveback == "html":
                return urlcontent.read()
            
            # give back pure nothing if giveback is "nothing" - useful for POST requests
            if giveback == "nothing":
                # do some cleanup
                del urlcontent
                del passman
                del auth_handler
                return None           
            
            # the heart of the whole Nagios-status-monitoring engine:
            # first step: parse the read HTML
            naghtml = lxml.etree.HTML(urlcontent.read())
                
            # second step: make pretty HTML of it
            nagprettyhtml = lxml.etree.tostring(copy.copy(naghtml), pretty_print=True)
            
            # third step: clean HTML from tags which embarass libxml2 2.7
            # only possible when module lxml.html.clean has been loaded
            if sys.modules.has_key("lxml.html.clean"):
                # clean html from tags which libxml2 2.7 is worried about
                # this is the case with all tags that do not need a closing end tag like link, br, img
                cleaner = lxml.html.clean.Cleaner(remove_tags=["link", "br", "img"], page_structure=True, style=False)
                nagprettyhtml = copy.copy(cleaner.clean_html(nagprettyhtml))
                
                # lousy workaround for libxml2 2.7 which worries about attributes without value
                # we hope that nobody names a server '" nowrap>' - chances are pretty small because this "name"
                # contains unallowed characters and is far from common sense
                nagprettyhtml = nagprettyhtml.replace('" nowrap>', '">')

            # fourth step: make objects of tags for easy access
            htobj = copy.copy(lxml.objectify.fromstring(nagprettyhtml))
            
        except:
            # do some cleanup
            del passman
            del auth_handler
            return "ERROR"

        #do some cleanup
        del passman
        del auth_handler
        del urlcontent
        del cleaner
        del naghtml
        del nagprettyhtml

        # give back HTML object from Nagios webseite
        return htobj
    
    
00551     def GetHost(self, host):
        """
        find out ip or hostname of given host to access hosts/devices which do not appear in DNS but
        have their ip saved in Nagios
        """
        
        # initialize ip string
        ip = ""

        # glue nagios cgi url and hostinfo 
        nagcgiurl_host  = self.nagios_cgi_url + "/extinfo.cgi?type=1&host=" + host
        
        # get host info
        htobj = self.FetchURL(nagcgiurl_host, giveback="dict")
        
        try:
            # take ip from object path
            ip = str(htobj.body.table.tr.td[1].div[5].text)
            # Workaround for Nagios 3.1 where there are groups listed whose the host is a member of
            if ip == "Member of":
                ip = str(htobj.body.table.tr.td[1].div[7].text)
            # print IP in debug mode
            if str(self.conf.debug_mode) == "True":    
                print "IP of %s:" % (host), ip
            # when connection by DNS is not configured do it by IP
            if str(self.conf.connect_by_dns_yes) == "True":
                # try to get DNS name for ip, if not available use ip
                try:
                    host = socket.gethostbyaddr(ip)[0]
                except:
                    host = ip
            else:
                host = ip
        except:
            host = "ERROR"
         
        # do some cleanup
        del htobj    

        # give back host or ip
        return host
    
    
00594     def __del__(self):
        """
        hopefully a __del__() method may make this object better collectable for gc
        """
        del(self)
    
    
00601 class NagiosHost(object):
    """
        one host which is monitored by a Nagios server, gets populated with services
    """
    def __init__(self):
        self.name = ""
        self.status = ""
        self.last_check = ""
        self.duration = ""
        self.attempt = ""
        self.status_information = ""
        self.services = dict()
        
        
00615     def __del__(self):
        """
        hopefully a __del__() method may make this object better collectable for gc
        """
        del(self)
        
        
00622 class NagiosService(object):
    """
        one service which runs on a host
    """
    def __init__(self):
        self.name = ""
        self.host = ""
        self.status = ""
        self.last_check = ""
        self.duration = ""
        self.attempt = ""
        self.status_information = ""
        
        
00636     def __del__(self):
        """
        hopefully a __del__() method may make this object better collectable for gc
        """
        del(self)
    

Generated by  Doxygen 1.6.0   Back to index