#!/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
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
__cmdParser__ = OptionParser()
__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="append", \
dest="noplugins",\
help="list of plugin _not_ 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("-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)
g.runOnce()
screen.finish()
for rrr in range(0, optDicCounter):
optDic[rrr][3]['enabled'] = plugCbox.getEntryValue(rrr)[1]
out.append((optDic[rrr]))
return out
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)) ):
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
# 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:
get_curse_options(alloptions)
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)
# 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()
# 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()
# 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()
# 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()
# 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()