|
Server : Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8e-fips-rhel5 DAV/2 PHP/5.2.17 System : Linux localhost 2.6.18-419.el5 #1 SMP Fri Feb 24 22:47:42 UTC 2017 x86_64 User : nobody ( 99) PHP Version : 5.2.17 Disable Function : NONE Directory : /proc/21572/root/usr/lib/python2.4/site-packages/sos/ |
Upload File : |
## plugintools.py
## This exports methods available for use by plugins for sos
## 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 = R0902
# pylint: disable-msg = R0904
# pylint: disable-msg = W0702
# pylint: disable-msg = W0703
# pylint: disable-msg = R0201
# pylint: disable-msg = W0611
# pylint: disable-msg = W0613
"""
This is the base class for sosreport plugins
"""
from sos.helpers import *
from threading import Thread, activeCount
import os, os.path, sys, string, itertools, glob, re, traceback
import logging
from stat import *
from time import time
import fnmatch
# python < 2.4 (RHEL3 and RHEL4) doesn't have format_exc, activate work-around
if sys.version_info[0] <= 2 and sys.version_info[1] < 4:
def format_exc():
import StringIO
output = StringIO.StringIO()
traceback.print_exc(file = output)
toret = output.getvalue()
output.close()
return toret
traceback.format_exc = format_exc
class PluginBase:
"""
Base class for plugins
"""
def __init__(self, pluginname, commons):
# pylint: disable-msg = E0203
try:
len(self.optionList)
except:
self.optionList = []
# pylint: enable-msg = E0203
self.copiedFiles = []
self.copiedDirs = []
self.executedCommands = []
self.diagnose_msgs = []
self.alerts = []
self.customText = ""
self.optNames = []
self.optParms = []
self.piName = pluginname
self.cInfo = commons
self.forbiddenPaths = []
self.copyPaths = []
self.collectProgs = []
self.thread = None
self.pid = None
self.eta_weight = 1
self.time_start = None
self.time_stop = None
self.soslog = logging.getLogger('sos')
# get the option list into a dictionary
for opt in self.optionList:
self.optNames.append(opt[0])
self.optParms.append({'desc':opt[1], 'speed':opt[2], 'enabled':opt[3]})
# Method for applying regexp substitutions
def doRegexSub(self, srcpath, regexp, subst):
'''Apply a regexp substitution to a file archived by sosreport.
'''
if len(self.copiedFiles):
for afile in self.copiedFiles:
if fnmatch.fnmatch(afile['srcpath'],srcpath):
abspath = os.path.join(self.cInfo['dstroot'],
afile['srcpath'].lstrip(os.path.sep))
try:
fp = open(abspath, 'r')
tmpout, occurs = re.subn( regexp, subst, fp.read() )
fp.close()
if occurs > 0:
fp = open(abspath,'w')
fp.write(tmpout)
fp.close()
return occurs
except SystemExit:
raise SystemExit
except KeyboardInterrupt:
raise KeyboardInterrupt
except Exception, e:
self.soslog.log(logging.VERBOSE, "Problem at path %s (%s)" % (abspath,e))
break
return False
def doRegexFindAll(self,regex,fname):
''' Return a list of all non overlapping matches in the string(s)
'''
out=[]
f=open(fname,'r')
content=f.read()
f.close()
reg=re.compile(regex,re.MULTILINE)
for i in reg.findall(content):
out.append(i)
return out
# Methods for copying files and shelling out
def doCopyFileOrDir(self, srcpath):
# pylint: disable-msg = R0912
# pylint: disable-msg = R0915
''' Copy file or directory to the destination tree. If a directory, then everything
below it is recursively copied. A list of copied files are saved for use later
in preparing a report
'''
copyProhibited = 0
for path in self.forbiddenPaths:
if ( srcpath.count(path) > 0 ):
copyProhibited = 1
if copyProhibited:
return ''
if not os.path.exists(srcpath):
self.soslog.debug("file or directory %s does not exist" % srcpath)
return
if os.path.islink(srcpath):
# This is a symlink - We need to also copy the file that it points to
# FIXME: ignore directories for now
if os.path.isdir(srcpath):
return
link = os.readlink(srcpath)
# What's the name of the symlink on the dest tree?
dstslname = os.path.join(self.cInfo['dstroot'], srcpath.lstrip(os.path.sep))
if os.path.isabs(link):
# the link was an absolute path, and will not point to the new
# tree. We must adjust it.
rpth = sosRelPath(os.path.dirname(dstslname), os.path.join(self.cInfo['dstroot'], link.lstrip(os.path.sep)))
else:
# no adjustment, symlink is the relative path
rpth = link
# make sure the link doesn't already exists
if os.path.exists(dstslname):
self.soslog.log(logging.DEBUG, "skipping symlink creation: already exists (%s)" % dstslname)
return
# make sure the dst dir exists
if not (os.path.exists(os.path.dirname(dstslname)) and os.path.isdir(os.path.dirname(dstslname))):
os.makedirs(os.path.dirname(dstslname))
self.soslog.log(logging.VERBOSE3, "creating symlink %s -> %s" % (dstslname, rpth))
os.symlink(rpth, dstslname)
if os.path.isabs(link):
self.doCopyFileOrDir(link)
else:
self.doCopyFileOrDir(os.path.join(os.path.dirname(srcpath), link))
self.copiedFiles.append({'srcpath':srcpath, 'dstpath':rpth, 'symlink':"yes", 'pointsto':link})
return
else: # not a symlink
if os.path.isdir(srcpath):
for afile in os.listdir(srcpath):
if afile == '.' or afile == '..':
pass
else:
self.doCopyFileOrDir(srcpath+'/'+afile)
return
# if we get here, it's definitely a regular file (not a symlink or dir)
self.soslog.log(logging.VERBOSE3, "copying file %s" % srcpath)
try:
tdstpath, abspath = self.__copyFile(srcpath)
except "AlreadyExists":
self.soslog.log(logging.DEBUG, "error copying file %s (already exists)" % (srcpath))
return
except IOError, e:
self.soslog.warning("error copying file %s (IOError)" % (srcpath))
raise e
except Exception, e:
self.soslog.log(logging.VERBOSE2, "error copying file %s (%s)" % (srcpath, e))
return
self.copiedFiles.append({'srcpath':srcpath, 'dstpath':tdstpath, 'symlink':"no"}) # save in our list
return abspath
def __copyFile(self, src):
""" call cp to copy a file, collect return status and output. Returns the
destination file name.
"""
try:
# pylint: disable-msg = W0612
status, shout, runtime = sosGetCommandOutput("/bin/cp --parents -P --preserve=mode,ownership,timestamps,links " + src +" " + self.cInfo['dstroot'])
if status:
self.soslog.debug(shout)
abspath = os.path.join(self.cInfo['dstroot'], src.lstrip(os.path.sep))
relpath = sosRelPath(self.cInfo['rptdir'], abspath)
return relpath, abspath
except SystemExit:
raise SystemExit
except KeyboardInterrupt:
raise KeyboardInterrupt
except Exception,e:
self.soslog.warning("Problem copying file %s (%s)" % (src, e))
def addForbiddenPath(self, forbiddenPath):
"""Specify a path to not copy, even if it's part of a copyPaths[] entry.
"""
# Glob case handling is such that a valid non-glob is a reduced glob
for filespec in glob.glob(forbiddenPath):
self.forbiddenPaths.append(filespec)
def getAllOptions(self):
"""
return a list of all options selected
"""
return (self.optNames, self.optParms)
def setOption(self, optionname, enable):
''' enable or disable the named option.
'''
for name, parms in zip(self.optNames, self.optParms):
if name == optionname:
parms['enabled'] = enable
def isOptionEnabled(self, optionname):
''' see whether the named option is enabled.
'''
for name, parms in zip(self.optNames, self.optParms):
if name == optionname:
return parms['enabled']
# nonexistent options aren't enabled.
return 0
def addCopySpecLimit(self,copyspec,sizelimit = 0):
"""Add a file specification (with limits)
"""
if (sizelimit == 0):
return self.addCopySpec(copyspec)
files = glob.glob(copyspec)
if (not len(files)):
self.soslog.debug("glob %s is empty" % copyspec)
return
files.sort()
cursize = 0
limit_reached = False
sizelimit *= 1024 * 1024 # in MB
for flog in files:
cursize += os.stat(flog)[ST_SIZE]
if sizelimit and cursize > sizelimit:
limit_reached = True
break
self.addCopySpec(flog)
# Truncate the first file (others would likely be compressed),
# ensuring we get at least some logs
if flog == files[0] and limit_reached:
self.collectExtOutput("tail -c%d %s" % (sizelimit, flog),
"tail_" + os.path.basename(flog), flog[1:])
def addCopySpec(self, copyspec):
""" Add a file specification (can be file, dir,or shell glob) to be
copied into the sosreport by this module
"""
# Glob case handling is such that a valid non-glob is a reduced glob
for filespec in glob.glob(copyspec):
self.copyPaths.append(filespec)
def copyFileGlob(self, srcglob):
""" Deprecated - please modify modules to use addCopySpec()
"""
sys.stderr.write("Warning: thecopyFileGlob() function has been deprecated. Please")
sys.stderr.write("use addCopySpec() instead. Calling addCopySpec() now.")
self.addCopySpec(srcglob)
def copyFileOrDir(self, srcpath):
""" Deprecated - please modify modules to use addCopySpec()
"""
sys.stderr.write("Warning: the copyFileOrDir() function has been deprecated. Please\n")
sys.stderr.write("use addCopySpec() instead. Calling addCopySpec() now.\n")
raise ValueError
#self.addCopySpec(srcpath)
def runExeInd(self, exe):
""" Deprecated - use callExtProg()
"""
sys.stderr.write("Warning: the runExeInd() function has been deprecated. Please use\n")
sys.stderr.write("the callExtProg() function. This should only be called\n")
sys.stderr.write("if collect() is overridden.")
pass
def callExtProg(self, prog):
""" Execute a command independantly of the output gathering part of
sosreport
"""
# Log if binary is not runnable or does not exist
if not os.access(prog.split()[0], os.X_OK):
self.soslog.log(logging.VERBOSE, "binary '%s' does not exist or is not runnable" % prog.split()[0])
# pylint: disable-msg = W0612
status, shout, runtime = sosGetCommandOutput(prog)
return status
def callExtProgWithOutput(self, prog):
""" Execute a command independantly of the output gathering part of
sosreport
"""
# Log if binary is not runnable or does not exist
if not os.access(prog.split()[0], os.X_OK):
self.soslog.log(logging.VERBOSE, "binary '%s' does not exist or is not runnable" % prog.split()[0])
# pylint: disable-msg = W0612
return sosGetCommandOutput(prog)
def runExe(self, exe):
""" Deprecated - use collectExtOutput()
"""
sys.stderr.write("Warning: the runExe() function has been deprecated. Please use\n")
sys.stderr.write("the collectExtOutput() function.\n")
pass
def collectExtOutput(self, exe, suggest_filename = None, symlink = None):
"""
Run a program and collect the output
"""
self.collectProgs.append( (exe,suggest_filename,symlink) )
def fileGrep(self, regexp, fname):
results = []
fp = open(fname, "r")
for line in fp.readlines():
if re.match(regexp, line):
results.append(line)
fp.close()
return results
def mangleCommand(self, exe):
# FIXME: this can be improved
mangledname = re.sub(r"^/(usr/|)(bin|sbin)/", "", exe)
mangledname = re.sub(r"[^\w\-\.\/]+", "_", mangledname)
mangledname = re.sub(r"/", ".", mangledname).strip(" ._-")[0:64]
return mangledname
def makeCommandFilename(self, exe):
""" The internal function to build up a filename based on a command """
outfn = self.cInfo['cmddir'] + "/" + self.piName + "/" + self.mangleCommand(exe)
# check for collisions
if os.path.exists(outfn):
inc = 2
while True:
newfn = "%s_%d" % (outfn, inc)
if not os.path.exists(newfn):
outfn = newfn
break
inc +=1
return outfn
def collectOutputNow(self, exe, suggest_filename = None, symlink = False):
""" Execute a command and save the output to a file for inclusion in
the report
"""
# First check to make sure the binary exists and is runnable.
if not os.access(exe.split()[0], os.X_OK):
self.soslog.log(logging.VERBOSE, "binary '%s' does not exist or is not runnable, trying anyways" % exe.split()[0])
# FIXME: we should have a timeout or we may end waiting forever
# pylint: disable-msg = W0612
status, shout, runtime = sosGetCommandOutput(exe)
if suggest_filename:
outfn = self.makeCommandFilename(suggest_filename)
else:
outfn = self.makeCommandFilename(exe)
if not os.path.isdir(os.path.dirname(outfn)):
os.mkdir(os.path.dirname(outfn))
if not (status == 127 or status == 32512):
try:
outfd = open(outfn, "w")
if len(shout):
outfd.write(shout+"\n")
outfd.close()
except IOError, e:
raise e
if symlink:
dst_from_root = outfn[len(self.cInfo['dstroot'])+1:]
target = ("../" * string.count(symlink, "/")) + dst_from_root
curdir = os.getcwd()
os.chdir(self.cInfo['dstroot'])
try:
os.symlink(target, symlink.strip("/."))
except:
pass
os.chdir(curdir)
outfn_strip = outfn[len(self.cInfo['cmddir'])+1:]
else:
self.soslog.log(logging.VERBOSE, "could not run command: %s" % exe)
outfn = None
outfn_strip = None
# sosStatus(status)
# save info for later
self.executedCommands.append({'exe': exe, 'file':outfn_strip}) # save in our list
self.cInfo['xmlreport'].add_command(cmdline=exe,exitcode=status,f_stdout=outfn_strip,runtime=runtime)
return outfn
def writeTextToCommand(self, exe, text):
""" A function that allows you to write a random text string to the
command output location referenced by exe; this is useful if you want
to conditionally collect information, but still want the output file
to exist so as not to confuse readers """
outfn = self.makeCommandFilename(exe)
if not os.path.isdir(os.path.dirname(outfn)):
os.mkdir(os.path.dirname(outfn))
outfd = open(outfn, "w")
outfd.write(text)
outfd.close()
self.executedCommands.append({'exe': exe, 'file': outfn}) # save in our list
return outfn
# For adding warning messages regarding configuration sanity
def addDiagnose(self, alertstring):
""" Add a configuration sanity warning for this plugin. These
will be displayed on-screen before collection and in the report as well.
"""
self.diagnose_msgs.append(alertstring)
return
# For adding output
def addAlert(self, alertstring):
""" Add an alert to the collection of alerts for this plugin. These
will be displayed in the report
"""
self.alerts.append(alertstring)
return
def addCustomText(self, text):
""" Append text to the custom text that is included in the report. This
is freeform and can include html.
"""
self.customText = self.customText + text
return
def doCollect(self):
""" This function has been replaced with copyStuff(threaded = True). Please change your
module calls. Calling setup() now.
"""
return self.copyStuff(threaded = True)
def isRunning(self):
"""
if threaded, is thread running ?
"""
if self.thread: return self.thread.isAlive()
return None
def wait(self,timeout=None):
"""
wait for a thread to complete - only called for threaded execution
"""
self.thread.join(timeout)
return self.thread.isAlive()
def copyStuff(self, threaded = False, semaphore = None):
"""
Collect the data for a plugin
"""
if threaded and self.thread == None:
self.thread = Thread(target=self.copyStuff, name=self.piName+'-thread', args = [True, semaphore] )
self.thread.start()
return self.thread
if semaphore: semaphore.acquire()
self.soslog.log(logging.VERBOSE, "starting threaded plugin %s" % self.piName)
self.time_start = time()
self.time_stop = None
for path in self.copyPaths:
self.soslog.debug("copying pathspec %s" % path)
try:
self.doCopyFileOrDir(path)
# propagate IO exceptions to caller
except IOError, e:
raise e
except SystemExit:
if threaded:
return SystemExit
else:
raise SystemExit
except KeyboardInterrupt:
if threaded:
return KeyboardInterrupt
else:
raise KeyboardInterrupt
except Exception, e:
self.soslog.log(logging.VERBOSE2, "error copying from pathspec %s (%s), traceback follows:" % (path,e))
self.soslog.log(logging.VERBOSE2, traceback.format_exc())
for (prog,suggest_filename,symlink) in self.collectProgs:
self.soslog.debug("collecting output of '%s'" % prog)
try:
self.collectOutputNow(prog, suggest_filename, symlink)
# propagate IO exceptions to caller
except IOError, e:
raise e
except SystemExit:
if threaded:
return SystemExit
else:
raise SystemExit
except KeyboardInterrupt:
if threaded:
return KeyboardInterrupt
else:
raise KeyboardInterrupt
except:
self.soslog.log(logging.VERBOSE2, "error collection output of '%s', traceback follows:" % prog)
self.soslog.log(logging.VERBOSE2, traceback.format_exc())
self.time_stop = time()
if semaphore: semaphore.release()
self.soslog.log(logging.VERBOSE, "plugin %s returning" % self.piName)
sys.stdout.write("\r plugin %s finished ... " % (self.piName,))
sys.stdout.flush()
def get_description(self):
""" This function will return the description for the plugin"""
try:
return self.__doc__.strip()
except:
return "<no description available>"
def checkenabled(self):
""" This function can be overidden to let the plugin decide whether
it should run or not.
"""
return True
def defaultenabled(self):
"""This devices whether a plugin should be automatically loaded or
only if manually specified in the command line."""
return True
def collect(self):
""" This function has been replaced with setup(). Please change your
module calls. Calling setup() now.
"""
self.setup()
def diagnose(self):
"""This class must be overridden to check the sanity of the system's
configuration before the collection begins.
"""
pass
def setup(self):
"""This class must be overridden to add the copyPaths, forbiddenPaths,
and external programs to be collected at a minimum.
"""
pass
def analyze(self):
"""
perform any analysis. To be replaced by a plugin if desired
"""
pass
def postproc(self):
"""
perform any postprocessing. To be replaced by a plugin if desired
"""
pass
def report(self):
""" Present all information that was gathered in an html file that allows browsing
the results.
"""
# make this prettier
html = '<hr/><a name="%s"></a>\n' % self.piName
# Intro
html = html + "<h2> Plugin <em>" + self.piName + "</em></h2>\n"
# Files
if len(self.copiedFiles):
html = html + "<p>Files copied:<br><ul>\n"
for afile in self.copiedFiles:
html = html + '<li><a href="%s">%s</a>' % (afile['dstpath'], afile['srcpath'])
if (afile['symlink'] == "yes"):
html = html + " (symlink to %s)" % afile['pointsto']
html = html + '</li>\n'
html = html + "</ul></p>\n"
# Dirs
if len(self.copiedDirs):
html = html + "<p>Directories Copied:<br><ul>\n"
for adir in self.copiedDirs:
html = html + '<li><a href="%s">%s</a>\n' % (adir['dstpath'], adir['srcpath'])
if (adir['symlink'] == "yes"):
html = html + " (symlink to %s)" % adir['pointsto']
html = html + '</li>\n'
html = html + "</ul></p>\n"
# Command Output
if len(self.executedCommands):
html = html + "<p>Commands Executed:<br><ul>\n"
# convert file name to relative path from our root
for cmd in self.executedCommands:
if cmd["file"] and len(cmd["file"]):
cmdOutRelPath = sosRelPath(self.cInfo['rptdir'], self.cInfo['cmddir'] + "/" + cmd['file'])
html = html + '<li><a href="%s">%s</a></li>\n' % (cmdOutRelPath, cmd['exe'])
else:
html = html + '<li>%s</li>\n' % (cmd['exe'])
html = html + "</ul></p>\n"
# Alerts
if len(self.alerts):
html = html + "<p>Alerts:<br><ul>\n"
for alert in self.alerts:
html = html + '<li>%s</li>\n' % alert
html = html + "</ul></p>\n"
# Custom Text
if (self.customText != ""):
html = html + "<p>Additional Information:<br>\n"
html = html + self.customText + "</p>\n"
return html