aboutsummaryrefslogblamecommitdiffstats
path: root/src/sosreport
blob: 34ecc5ec20fe2bdbc01f971d4c2e1e75efe1739c (plain) (tree)
1
2
3
4
5
6
7
                     



                                                             
               
                                                  
















                                                                        


                             

          
              
                                         


                         


                                                    
                                                                               

                                                                   
                                   


                 


                                                            
                                                                                            
                                  

                                               


                                                                                                    
                                          
                                                            
                                                                              
                   

    
                                           

                                              
    
                                        
                                            
                                              



                                                                           
 





                                           

                    














                                                                              
                                                                     
                                                           



                                                                      


                                                                                   


                                                                       
                                                               
                                       
                                                          
                                                                 

                                                    


                                                                                               


                                                                          



                                                                                                                   

                                  


                                                        
                                                                                   

                 

                                           


                                   

                   
                                               
 
                 
 

                     


                                           

                                                             




                                     



                                                                                                                      






                                                                
                        


                   


                                            


                                                                   


              














































                                                                                                                                  

                





                                                                                       


                      
                                                                






                                                                    
                                                  
 


                                                










                                                     

                                                                                         

 


                                      

                                    
                                   










                                                                      
                                                                                                                                                                                                                                       





                                                                
                                                                   


                                                                            
                                                                                    


                                                                       
                   
                                    


                                                        



                                             



                                         
                                         
                                                                 
            

                                                         


                                                                         




                                                                     
                                                                             
                           



                                             

                                                             
                                     

                                                               


                                              

                                                               
                                          
 



                                                          

                                           
                                         
                                                           
                    


                                       
 

                                             


                                                  
                            
             
                                
                                       



                                           
                         

                                                 




                                                                   
                                           
                                  
                             


                                             
                                         
                                                              
                      




                                       

















                                                                                                                 





                                                             







                                                                                      


                                                                                   
                            























                                                     



                                                 












                                                                         

                          


                             
                    
#!/usr/bin/env python
"""
Gather information about a system and report it using plugins
supplied for application-specific information
"""
## sosreport.py
## gather information about a system and report it

## Copyright (C) 2006 Steve Conklin <sconklin@redhat.com>

### This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.

## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.

## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

# pylint: disable-msg = W0611
# pylint: disable-msg = W0702

import sys
import os
#import curses
from optparse import OptionParser, Option
import sos.policyredhat
from sos.helpers import *
from snack import *
from threading import Thread, activeCount, enumerate
import signal

__breakHits__ = 0  # Use this to track how many times we enter the exit routine

## Set up routines to be linked to signals for termination handling
def exittermhandler(signum, frame):
    doExitCode()

def doExitCode():
    global __breakHits__
    __breakHits__ += 1
    if ( ( activeCount() > 1 ) and ( __breakHits__ == 1 ) ):
        print "SIGTERM received, multiple threads detected, waiting for all threads to exit"
        for thread in enumerate():
            thread.wait()
        print "All threads ended, cleaning up."
    if ( ( activeCount() > 1 ) and ( __breakHits__ > 1 ) ):
        print "Multiple SIGTERMs, multiple threads, attempting to signal threads to die immediately"
        ## FIXME: Add thread-kill code (see FIXME below)
        print "Threads dead, cleaning up."
    if ( ( activeCount() == 1 ) and ( __breakHits__ > 2 ) ):
        print "Multiple SIGTERMs, single thread, exiting without cleaning up."
        sys.exit(3)
    

    # FIXME: Add code here to clean up /tmp
    print "Calling sys.exit from doExitCode()"
    sys.exit("Abnormal exit")
    
# Handle any sort of exit signal cleanly
# Currently, we intercept only sig 15 (TERM)
signal.signal(signal.SIGTERM, exittermhandler)

## FIXME: Need to figure out how to IPC with child threads in case of
## multiple SIGTERMs.
## FIXME: Need to figure out how to handle SIGKILL - we can't intercept it.


if os.getuid() != 0:
    print 'You must run sosreport as root!'
    sys.exit(1)

# for debugging
__raisePlugins__ = 1

class SosOption (Option):
    """Allow to specify comma delimited list of plugins"""
    ACTIONS = Option.ACTIONS + ("extend",)
    STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
    TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
    ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)

    def take_action(self, action, dest, opt, value, values, parser):
        if action == "extend":
            lvalue = value.split(",")
            values.ensure_value(dest, []).extend(lvalue)
        else:
            Option.take_action(self, action, dest, opt, value, values, parser)

__cmdParser__ = OptionParser(option_class=SosOption)
__cmdParser__.add_option("-a", "--alloptions", action="store_true", \
                     dest="usealloptions", default=False, \
                     help="Use all options for loaded plugins")
__cmdParser__.add_option("-f", "--fastoptions", action="store_true", \
                     dest="fastoptions", default=False, \
                     help="Use only fast options for loaded plugins")
__cmdParser__.add_option("-g", "--gatheronly", action="store_true", \
                     dest="gatheronly", default=False, \
                     help="Gather information locally but don't package or submit")
__cmdParser__.add_option("-l", "--list-plugins", action="store_true", \
                     dest="listPlugins", default=False, \
                     help="list existing plugins")
__cmdParser__.add_option("-n", "--noplugin", action="extend", \
                     dest="noplugins",\
                     help="list of plugins _not_ to load")
__cmdParser__.add_option("-o", "--onlyplugin", action="extend", \
                     dest="onlyplugins",\
                     help="list of plugins to load")
__cmdParser__.add_option("-v", "--verbose", action="count", \
                     dest="verbosity", \
                     help="How obnoxious we're being about telling the user what we're doing.")
__cmdParser__.add_option("-b", "--no-progressbar", action="store_false", \
                     dest="progressbar", default=True, \
                     help="Do not display a progress bar.")
__cmdParser__.add_option("-m", "--multithreaded", action="store_true", \
                     dest="multithread", \
                     help="Use the multithreaded information gathering mode to speed up the report (experimental)")
(__cmdLineOpts__, __cmdLineArgs__)=__cmdParser__.parse_args()

def get_curse_options(alloptions):
    """
    use curses to enable the user to select some options
    """
    # alloptions is an array of (plug, plugname, optname, parms(dictionary)) tuples
    plugName = []
    out = []
    
    # get a sorted list of all plugin names
    for rrr in alloptions:
        if rrr[1] not in plugName:
            plugName.append(rrr[1])

    plugName.sort()
    plugCbox = CheckboxTree(height=5, scroll=1)

    countOpt = -1

    optDic = {}
    optDicCounter = 0

    # iterate over all plugins with options
    for curPlugName in plugName:
        plugCbox.addItem(curPlugName, (snackArgs['append'],))
        countOpt = countOpt+1

        for opt in alloptions:
            if opt[1] != curPlugName:
                continue

            snt = opt[2] + " ("+opt[3]['desc']+") is " + opt[3]['speed']
            plugCbox.addItem(snt, (countOpt, snackArgs['append']), item = optDicCounter, selected = opt[3]['enabled'])
            optDic[optDicCounter] = opt
            optDicCounter += 1
            

    screen = SnackScreen()
    bb = ButtonBar(screen, (("Ok", "ok"), ("Cancel", "cancel")))
    g = GridForm(screen, "Select Sosreport Options", 1, 10)
    g.add(plugCbox, 0, 0)
    g.add(bb, 0, 1, growx = 1)
    result = g.runOnce()

    screen.finish()

    if bb.buttonPressed(result) == "cancel":
        raise "Cancelled"

    for rrr in range(0, optDicCounter):
        optDic[rrr][3]['enabled'] =  plugCbox.getEntryValue(rrr)[1]
        out.append((optDic[rrr]))

    return out

class progressBar:
    def __init__(self, minValue = 0, maxValue = 10, totalWidth=12):
        self.progBar = "[]"   # This holds the progress bar string
        self.min = minValue
        self.max = maxValue
        self.span = maxValue - minValue
        self.width = totalWidth
        self.amount = 0       # When amount == max, we are 100% done 
        self.updateAmount(0)  # Build progress bar string

    def updateAmount(self, newAmount = 0):
        if newAmount < self.min: newAmount = self.min
        if newAmount > self.max: newAmount = self.max
        self.amount = newAmount

        # Figure out the new percent done, round to an integer
        diffFromMin = float(self.amount - self.min)
        percentDone = (diffFromMin / float(self.span)) * 100.0
        percentDone = round(percentDone)
        percentDone = int(percentDone)

        # Figure out how many hash bars the percentage should be
        allFull = self.width - 2
        numHashes = (percentDone / 100.0) * allFull
        numHashes = int(round(numHashes))

        # build a progress bar with hashes and spaces
        self.progBar = " [" + '#'*numHashes + ' '*(allFull-numHashes) + "]"

        # figure out where to put the percentage, roughly centered
        percentPlace = (len(self.progBar) / 2) - len(str(percentDone)) 
        percentString = str(percentDone) + "%"

        # slice the percentage into the bar
        self.progBar = " Progress" + self.progBar[0:percentPlace] + percentString + self.progBar[percentPlace+len(percentString):]

    def incAmount(self, toInc = 1):
        self.updateAmount(self.amount+toInc)

    def finished(self):
        self.updateAmount(self.max)
        sys.stdout.write(self.progBar + '\n')
        sys.stdout.flush()

    def update(self):
        sys.stdout.write(self.progBar + '\r')
        sys.stdout.flush()
    
def sosreport():
    # pylint: disable-msg = R0912
    # pylint: disable-msg = R0914
    # pylint: disable-msg = R0915
    """
    This is the top-level function that gathers and processes all sosreport information
    """
    loadedplugins = []
    alloptions = []

    # perhaps we should automatically locate the policy module??
    policy = sos.policyredhat.SosPolicy()

    # find the plugins path
    paths = sys.path
    for path in paths:
        if  path.strip()[-len("site-packages"):] == "site-packages":
            pluginpath = path + "/sos/plugins"
            reporterpath = path + "/sos/reporters"

    # Set up common info and create destinations
    
    dstroot = sosFindTmpDir()
    cmddir = os.path.join(dstroot, "sos_commands")
    logdir = os.path.join(dstroot, "sos_logs")
    rptdir = os.path.join(dstroot, "sos_reports")
    os.mkdir(cmddir, 0755)
    os.mkdir(logdir, 0755)
    os.mkdir(rptdir, 0755)

    # open log file
    logfd = open(logdir + "/sos.log", "w")

    # set up dict so everyone can share the following
    commons = {'dstroot': dstroot, 'cmddir': cmddir, 'logdir': logdir, 'rptdir': rptdir,
               'logfd': logfd, 'policy': policy, 'verbosity' : __cmdLineOpts__.verbosity}


    # Make policy aware of the commons
    policy.setCommons(commons)
    
    # validate and load plugins
    plugins = os.listdir(pluginpath)
    if __cmdLineOpts__.listPlugins:
        for plug in plugins:
            try:
                if ((plug[-3:] == '.py') and (plug != "__init__.py")):
                    plugbase =  plug[:-3]
                    pidot = "sos.plugins." + plugbase
                    print plugbase
            except:
                print "ouch"
        sys.exit()

    for plug in plugins:
        if ((plug[-3:] == '.py') and (plug != "__init__.py") and (not __cmdLineOpts__.noplugins or (plug[:-3] not in __cmdLineOpts__.noplugins)) and (not __cmdLineOpts__.onlyplugins or (plug[:-3] in __cmdLineOpts__.onlyplugins)) ):
            try:
                plugbase =  plug[:-3]
                pidot = "sos.plugins." + plugbase
                #print "importing plugin: %s" % plugbase
                try:
                    if policy.validatePlugin(pluginpath + plug):
                        pluginClass = importPlugin(pidot, plugbase)
                    else:
                        print "Plugin %s does not validate, skipping" % plug
                        continue
                    loadedplugins.append((plugbase, pluginClass(plugbase, commons)))
                except:
                    print "Plugin %s does not install, skipping" % plug
                    raise
            except:
                if __raisePlugins__:
                    raise
                print "plugin load failed for %s" % plug

    if not len(loadedplugins):
        print "no valid plugins were enabled"
        sys.exit(1)

    # Iterate over plugins for each stage

    # First, gather and process options
    for plugname, plug in loadedplugins:
        if __cmdLineOpts__.verbosity > 3:
            print "processing options from plugin: %s" % plugname
        try:
            len(__cmdLineOpts__.noplugins)
            if plugname not in __cmdLineOpts__.noplugins:
                names, parms = plug.getAllOptions()
                for optname, optparm  in zip(names, parms):
                    alloptions.append((plug, plugname, optname, optparm))
        except:
            names, parms = plug.getAllOptions()
            for optname, optparm  in zip(names, parms):
                alloptions.append((plug, plugname, optname, optparm))

    if not __cmdLineOpts__.fastoptions and not __cmdLineOpts__.usealloptions:
        if len(alloptions):
            try:
                get_curse_options(alloptions)
            except "Cancelled":
                sys.exit("Exiting.")
        else:
            print "no options available for selected plugins"
    elif __cmdLineOpts__.fastoptions:
        for i in range(len(alloptions)):
            for plug, plugname, optname, optparm in alloptions:
                if optparm['speed'] == 'fast':
                    plug.setOption(optname, 1)
    elif __cmdLineOpts__.alloptions:
        for i in range(len(alloptions)):
            for plug, plugname, optname, optparm in alloptions:
                plug.setOption(optname, 1)

    # Setup the progress bar
    if __cmdLineOpts__.progressbar:
        pbar = progressBar(0, len(loadedplugins) * 33, 40)

    # Call the setup method for each plugin
    for plugname, plug in loadedplugins:
        if __cmdLineOpts__.verbosity > 1:
            print "Setting up plugin module %s" % plugname,
        plug.setup()
        if __cmdLineOpts__.progressbar:
            pbar.incAmount()
            pbar.update()

    # Call the collect method for each plugin
    for plugname, plug in loadedplugins:
        if __cmdLineOpts__.verbosity > 0:
            print "Executing plugin %s" % plugname
        if __cmdLineOpts__.multithread:
            plug.doCollect()
        else:
            plug.copyStuff()    
        if __cmdLineOpts__.progressbar:
            if __cmdLineOpts__.multithread:
                pbar.incAmount()
            else:
                pbar.incAmount(30)
            pbar.update()

    # Wait for all the collection threads to exit
    if __cmdLineOpts__.multithread:
        for plugname, plug in loadedplugins:
            if __cmdLineOpts__.verbosity > 1:
                print "Waiting for plugin %s to return" % plugname,
            plug.wait()
            if __cmdLineOpts__.progressbar:
                pbar.incAmount(30)
                pbar.update()

    # Call the analyze method for each plugin
    for plugname, plug in loadedplugins:
        if __cmdLineOpts__.verbosity > 1:
            print "Analyzing results of plugin %s" % plugname,
        plug.analyze()
        if __cmdLineOpts__.progressbar:
            pbar.incAmount()
            pbar.update()

    pbar.finished()

    # Sort the module names to do the report in alphabetical order
    loadedplugins.sort()

    # Generate the header for the html output file
    rfd = open(rptdir + "/" + "sosreport.html", "w")
    rfd.write("""
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
        <head>
    <link rel="stylesheet" type="text/css" media="screen" href="donot.css" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Sos System Report</title>
        </head>

        <body>
    """)


    # Make a pass to gather Alerts and a list of module names
    allAlerts = []
    plugNames = []
    for plugname, plug in loadedplugins:
        for alert in plug.alerts:
            allAlerts.append('<a href="#%s">%s</a>: %s' % (plugname, plugname, alert))
        plugNames.append(plugname)



    # Create a table of links to the module info
    rfd.write("<hr/><h3>Loaded Plugins:</h3>")
    rfd.write("<table><tr>\n")
    rr = 0
    for i in range(len(plugNames)):
        rfd.write('<td><a href="#%s">%s</a></td>\n' % (plugNames[i], plugNames[i]))
        rr = divmod(i, 4)[1]
        if (rr == 3):
            rfd.write('</tr>')
    if not (rr == 3):
        rfd.write('</tr>')
    rfd.write('</table>\n')

    rfd.write('<hr/><h3>Alerts:</h3>')
    rfd.write('<ul>')
    for alert in allAlerts:
        rfd.write('<li>%s</li>' % alert)
    rfd.write('</ul>')


    # Call the report method for each plugin
    for plugname, plug in loadedplugins:
        html = plug.report()
        rfd.write(html)

    rfd.write("</body></html>")

    rfd.close()

    # Collect any needed user information (name, etc)

    # Close all log files and perform any cleanup
    logfd.close()

    # Call the postproc method for each plugin
    for plugname, plug in loadedplugins:
        plug.postproc()
  
    if __cmdLineOpts__.gatheronly:
        print "Collected information is in " + dstroot
        print "Your html report is in " + rptdir + "/" + "sosreport.html"
    else:
        # package up the results for the support organization
        policy.packageResults()
        # delete gathered files
        os.system("rm -rf %s" % dstroot)
        # automated submission will go here
 
    
if __name__ == '__main__':
    try:
        sosreport()
    except KeyboardInterrupt:
        doExitCode()