diff options
115 files changed, 4204 insertions, 2144 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..790380d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*~ +*.class +*swp* +*.pyc +*.sw* +tags +buildjar/ +gpgkeys/rhsupport.* +rpm-build/* @@ -7,9 +7,11 @@ Keith Kearnan <kearnan_keith@emc.com> Kent Lamb <klamb@redhat.com> Marc Sauton <msauton@redhat.com> Navid Sheikhol-Eslami <navid@redhat.com> +Pierre Amadio <pamadio@redhat.com> Pierre Carrier <pcarrier@redhat.com> Ranjith Rajaram <rrajaram@redhat.com> Sadique Puthen <sputhenp@redhat.com> Shijoe George <spanjikk@redhat.com> Steve Conklin <sconklin@redhat.com> Tomas Smetana <tsmetana@redhat.com> +John Berninger <jwb@redhat.com> diff --git a/ChangeLog.deprecated b/ChangeLog.deprecated deleted file mode 100644 index 92947fd9..00000000 --- a/ChangeLog.deprecated +++ /dev/null @@ -1,248 +0,0 @@ -2007-06-15 Navid Sheikhol-Eslami <navid@redhat.com> - - * [BZ#241282] initial port to RHEL4 :) sos core now runs happily, plugins will probably need to be fixed as well. - * Initial commit of XML reporting to gather details about commands executed and files gathered. - * Exceptions in plugin.analyse() were not catched allowing a bad plugin to break sosreport. - -2007-06-15 Eugene Teo <eteo@redhat.com> - - * lib/sos/plugins/apache.py, lib/sos/plugins/nfsserver.py, lib/sos/plugins/selinux.py, lib/sos/plugins/xinetd.py, lib/sos/plugins/ssh.py, lib/sos/plugins/sendmail.py, lib/sos/plugins/samba.py, lib/sos/plugins/named.py, lib/sos/plugins/cluster.py: - - Edited apache.py to gather /var/log/httpd/* log files - - Added nfsserver.py to gather NFS server-related debugging information - - Edited selinux.py to gather /etc/selinux/* configuration files - - Added xinetd.py to gather xinetd-related information - - Added ssh.py to gather ssh-related information - - Added sendmail.py to gather sendmail information - - Edited samba.py to gather /var/log/samba/* log files - - Edited named.py to gather /etc/sysconfig/named - - Edited cluster.py to gather the output of fdisk -l to show the - shared storage devices that should be available to each system - -2007-05-28 Eugene Teo <eteo@redhat.com> - - * lib/sos/plugins/systemtap.py: - - Added systemtap.py to gather SystemTap pre-requisites information - -2007-05-28 Eugene Teo <eteo@redhat.com> - - * lib/sos/plugins/amd.py: - - Added amd.py to gather Amd automounter information - -2007-05-25 Eugene Teo <eteo@redhat.com> - - * lib/sos/plugins/xen.py, lib/sos/plugins/pam.py, lib/sos/plugins/memory.py: - - Edited xen.py to determine if CPU has PAE/Intel VT/AMD-V support - - Edited pam.py to gather configurations in /etc/security, and files - listing of /lib/security/pam_*so - - Edited memory.py to gather /proc/{vmstat,slabinfo}, and free -m - output - -2007-04-23 Navid Sheikhol-Eslami <navid@redhat.com> - - * Running "multipath" without arguments might change the device-mapper maps, which we want to avoid if things are broken. - -2007-04-02 Navid Sheikhol-Eslami <navid@redhat.com> - - * Replaced xen plugin with (better) version from Chris Lalancette <clalance@redhat.com> - -2007-03-29 Navid Sheikhol-Eslami <navid@redhat.com> - - * Added a checkenabled() function which can be used to disable a plugin at run-time. - * Disable the progress-bar if verbosity is enabled. - -2007-03-27 Navid Sheikhol-Eslami <navid@redhat.com> - - * Fixed hardware plugin to use modules.pcimap instead of deprecated pcitable. - * Added a random suffix to sosreport tree to avoid overwriting an existing tree with same name. - * Better logging using python's logging module. - * Verbose logs included in sosreport (sos_logs/sos.log) - -2007-03-15 Navid Sheikhol-Eslami <navid@redhat.com> - - * Implemented a progress bar (RFE BZ#219672) which can be disabled from the command line. - * Added check to see if the loaded module matches the copy on the file-system - -2007-03-14 Navid Sheikhol-Eslami <navid@redhat.com> - - * fixed BZ#219877 (ncurses "Cancel" button makes sosreport exit) - -2007-03-07 Navid Sheikhol-Eslami <navid@redhat.com> - - * Allow passing multiple comma-separated plugin names to -n (--noplugin) and -o (--onlyplugin) options. - * Added further commands' output to gather from lvm_dump - - -2007-02-20 Navid Sheikhol-Eslami <navid@redhat.com> - - * Added a specialized plugin for device-mapper related configuration files and command outputs (device-mapper, LVM and multipath) - * Added --onlyplugin option (-o) to selectively choose which plugins to load (complementary to existing --noplugin) - * Exit if no valid plugin was selected (rather than building an empty sosreport). - -2007-02-16 Navid Sheikhol-Eslami <navid@redhat.com> - - * Strip out the shared secret (bindpw) from /etc/ldap.conf - * Collect parsed configuration tree directly from ccsd (useful for troubleshooting parsing issues) - * Scamble password information for fencing devices. - -2007-01-26 Navid Sheikhol-Eslami <navid@redhat.com> - - * Added doRegexSub() to be called in postproc() to apply a regexp substitution to files - * Added radius plugin for freeradius data collection - * Ask full name to prevent errors when moving the sos tree before packaging - * Reformat tar file name is no ticket number is given - -2006-06-19 Steve Conklin <sconklin@tintin> - - * ChangeLog, LICENSE, setup.py, sos.spec: - Added License file and bumped release - -2006-06-08 dlehman <dlehman@tintin> - - * example_plugins/example.py, example_plugins/fsusage.py, example_plugins/release.py, example_plugins/template.py, lib/sos/helpers.py, lib/sos/plugins/apache.py, lib/sos/plugins/bootloader.py, lib/sos/plugins/cluster.py, lib/sos/plugins/filesys.py, lib/sos/plugins/ftp.py, lib/sos/plugins/general.py, lib/sos/plugins/hardware.py, lib/sos/plugins/kernel.py, lib/sos/plugins/ldap.py, lib/sos/plugins/libraries.py, lib/sos/plugins/mail.py, lib/sos/plugins/memory.py, lib/sos/plugins/named.py, lib/sos/plugins/networking.py, lib/sos/plugins/pam.py, lib/sos/plugins/process.py, lib/sos/plugins/rhn.py, lib/sos/plugins/rpm.py, lib/sos/plugins/samba.py, lib/sos/plugins/selinux.py, lib/sos/plugins/squid.py, lib/sos/plugins/startup.py, lib/sos/plugins/system.py, lib/sos/plugins/tarball.py, lib/sos/plugins/x11.py, lib/sos/plugintools.py, lib/sos/policyredhat.py, setup.py, sosreport: - - Flesh out rhn plugin to handle Proxy or Satellite - - Add package queries to policyredhat.py (allPkgsByName, pkgByName, pkgNVRA) - - Add policy instance to the commons dict for access from plugins - - Use string objects' methods instead of the string module where possible - - Remove imports of unused string module - - Cleanup some typos, redundant initializations, &c - -2006-06-08 dlehman <dlehman@tintin> - - * example_plugins/example.py, example_plugins/fsusage.py, example_plugins/release.py, example_plugins/template.py, lib/sos/helpers.py, lib/sos/plugins/apache.py, lib/sos/plugins/bootloader.py, lib/sos/plugins/cluster.py, lib/sos/plugins/filesys.py, lib/sos/plugins/ftp.py, lib/sos/plugins/general.py, lib/sos/plugins/hardware.py, lib/sos/plugins/kernel.py, lib/sos/plugins/ldap.py, lib/sos/plugins/libraries.py, lib/sos/plugins/mail.py, lib/sos/plugins/memory.py, lib/sos/plugins/named.py, lib/sos/plugins/networking.py, lib/sos/plugins/pam.py, lib/sos/plugins/process.py, lib/sos/plugins/rhn.py, lib/sos/plugins/rpm.py, lib/sos/plugins/samba.py, lib/sos/plugins/selinux.py, lib/sos/plugins/squid.py, lib/sos/plugins/startup.py, lib/sos/plugins/system.py, lib/sos/plugins/tarball.py, lib/sos/plugins/x11.py, lib/sos/plugintools.py, lib/sos/policyredhat.py, setup.py, sosreport: - - Flesh out rhn plugin to handle Proxy or Satellite - - Add package queries to policyredhat.py (allPkgsByName, pkgByName, pkgNVRA) - - Add policy instance to the commons dict for access from plugins - - Use string objects' methods instead of the string module where possible - - Remove imports of unused string module - - Cleanup some typos, redundant initializations, &c - -2006-06-05 jwhiter <jwhiter@tintin> - - * lib/sos/plugins/system.py: - adding the abilit to capture the autofs maps to system.py - -2006-05-31 Steve Conklin <sconklin@tintin> - - * ChangeLog, Makefile: New Makefile and ChangeLog (autogenerated) - - * Changelog, setup.py, sos.spec: - Removed old Changelog file and sync'd version and Changelog in spec file - - * lib/sos/plugins/networking.py, TODO, setup.py, sos.spec: - Final patches and version change before submission to Fedora - -2006-05-31 Steve Conklin <sconklin@tintin> - - * Changelog, setup.py, sos.spec: - Removed old Changelog file and sync'd version and Changelog in spec file - - * lib/sos/plugins/networking.py, TODO, setup.py, sos.spec: - Final patches and version change before submission to Fedora - -2006-05-26 Steve Conklin <sconklin@tintin> - - * Changelog, lib/sos/helpers.py, lib/sos/plugintools.py, setup.py, sosreport: - Added pamadio's curses UI for selecting plugin options - Added flushing stdout after informational messages - -2006-05-26 jwhiter <jwhiter@tintin> - - * lib/sos/plugins/filesys.py: - - making the filesys.py plugin call 'blkid' when running at the request of L1, this will allow us to map labels with actual devices. - - * lib/sos/plugins/tarball.py, lib/sos/plugintools.py, sosreport: - - Adding tarball.py to create a tarball of the report after it is run - - Updated sosreport to call postproc() in all the plugins, which handles the post run - - Added runExeInd() which will just run the exe and return the status to plugintools.py - - Added postproc() to plugintools.py so that plugins can implement it - -2006-05-25 Steve Conklin <sconklin@tintin> - - * lib/sos/plugintools.py, sosreport: - Fixed file naming for commands to eliminate special chars and prevent - name collisions. Added sorting of plugins by name before reporting. - - * TODO, lib/sos/plugins/kernel.py, lib/sos/plugintools.py, setup.py: - Fixed option handling - -2006-05-23 jwhiter <jwhiter@tintin> - - * Changelog, lib/sos/plugins/kernel.py, setup.py: - Adding jwb's patch to have sosreport grab sysrq data. - -2006-05-22 Steve Conklin <sconklin@tintin> - - * lib/sos/plugins/apache.py: oops, forgot this file - - * lib/sos/plugins/bootloader.py, lib/sos/plugins/filesys.py, lib/sos/plugins/ftp.py, lib/sos/plugins/general.py, lib/sos/plugins/hardware.py, lib/sos/plugins/kernel.py, lib/sos/plugins/ldap.py, lib/sos/plugins/mail.py, lib/sos/plugins/memory.py, lib/sos/plugins/named.py, lib/sos/plugins/samba.py, lib/sos/plugins/squid.py, lib/sos/plugins/x11.py, sos.spec: - Patch from jwb - - * lib/sos/plugins/filesys.py, lib/sos/plugins/kernel.py: - jwb's patches for kernel.py and filesys.py - - * sosreport: minor fix to the dir perms patch - - * lib/sos/plugins/bootloader.py, lib/sos/plugins/cluster.py, lib/sos/plugins/filesys.py, lib/sos/plugins/general.py, lib/sos/plugins/hardware.py, lib/sos/plugins/kernel.py, lib/sos/plugins/libraries.py, lib/sos/plugins/memory.py, lib/sos/plugins/networking.py, lib/sos/plugins/pam.py, lib/sos/plugins/process.py, lib/sos/plugins/rhn.py, lib/sos/plugins/rpm.py, lib/sos/plugins/selinux.py, lib/sos/plugins/startup.py, lib/sos/plugins/system.py, lib/sos/plugins/template.py, lib/sos/plugins/x11.py, Changelog, example_plugins/template.py, lib/sos/plugintools.py, setup.py, sosreport: - Merged all of jwb's weekend patches. Make output dirs world readable, check before - executing executables, and added a lot of plugins. - -2006-05-19 Steve Conklin <sconklin@tintin> - - * lib/sos/plugins/template.py: Removed unneeded variabled from plugin. - - * Changelog, TODO, example_plugins/example.py, example_plugins/fsusage.py, example_plugins/release.py, example_plugins/runcommand.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, setup.py, sosreport: - Applied jwb's fix, added his examples, improved html output - -2006-05-18 Steve Conklin <sconklin@tintin> - - * example_plugins/example.py, example_plugins/runcommand.py, lib/sos/plugins/template.py: - Put instance variables in plugins in addition to base class - - * example_plugins/example.py, example_plugins/runcommand.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, setup.py, sosreport: - Removed separate pit class, and put everything having to do with the plugin in - pluginBase. Still incorrectly aggregates data across all plugins. - - * README: Added Jwb as a contributor - - * Changelog, TODO, lib/sos/plugins/template.py, lib/sos/plugintools.py, sosreport: - Implemented a base class for plugins - -2006-05-17 Steve Conklin <sconklin@tintin> - - * Changelog, TODO, lib/sos/helpers.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, sosreport, tests/maketesttree.sh: - Cleaned up code, added comments, fixed dir copying bug, changed option - handling. See Changelog - -2006-05-16 Steve Conklin <sconklin@tintin> - - * lib/sos/helpers.py, lib/sos/plugintools.py: - Missed an edit to change log file descriptor to the dictionary. Fixed. - - * TODO: Added documentation to list - - * TODO: Added need for example plugin - -2006-05-15 Steve Conklin <sconklin@tintin> - - * README, TODO, lib/sos/plugins/template.py, lib/sos/plugintools.py, setup.py, sosreport: - Added a dictionary of things that need to be known to all, like paths for - reports. - - Added Pierre Amadio's command line arg handling - - Fixed incorrect handling of command completion status - - Added html generation - -2006-05-09 Steve Conklin <sconklin@tintin> - - * TODO: changed it - - * setup.cfg: removed RFC file - - * MANIFEST.in, Notes.txt, README, TODO, lib/sos/__init__.py, lib/sos/helpers.py, lib/sos/plugins/__init__.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, lib/sos/policyredhat.py, setup.cfg, setup.py, sosreport: - Initial checkin of the sos project - - * MANIFEST.in, Notes.txt, README, TODO, lib/sos/__init__.py, lib/sos/helpers.py, lib/sos/plugins/__init__.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, lib/sos/policyredhat.py, setup.cfg, setup.py, sosreport: - New file. - @@ -9,6 +9,10 @@ REPO = http://svn.fedorahosted.org/svn/sos SUBDIRS = po sos sos/plugins PYFILES = $(wildcard *.py) +# OS X via brew +# MSGCAT = /usr/local/Cellar/gettext/0.18.1.1/bin/msgcat +MSGCAT = msgcat + RPM_BUILD_DIR = rpm-build RPM_DEFINES = --define "_topdir %(pwd)/$(RPM_BUILD_DIR)" \ @@ -19,6 +23,12 @@ RPM_DEFINES = --define "_topdir %(pwd)/$(RPM_BUILD_DIR)" \ --define "_sourcedir %{_topdir}" RPM = rpmbuild RPM_WITH_DIRS = $(RPM) $(RPM_DEFINES) +ARCHIVE_DIR = $(RPM_BUILD_DIR)/$(NAME)-$(VERSION) + +ARCHIVE_NAME = sosreport.zip +SRC_BUILD = $(RPM_BUILD_DIR)/sdist +PO_DIR = $(SRC_BUILD)/sos/po +ZIP_DEST = $(SRC_BUILD)/$(ARCHIVE_NAME) build: for d in $(SUBDIRS); do make -C $$d; [ $$? = 0 ] || exit 1 ; done @@ -37,14 +47,14 @@ install: install -m644 LICENSE README TODO $(DESTDIR)/usr/share/$(NAME)/. install -m644 $(NAME).conf $(DESTDIR)/etc/$(NAME).conf install -m644 gpgkeys/rhsupport.pub $(DESTDIR)/usr/share/$(NAME)/. - sed 's/@SOSVERSION@/$(VERSION)/g'<sos/__init__.py.in >sos/__init__.py + sed 's/@SOSVERSION@/$(VERSION)/g' < sos/__init__.py > sos/__init__.py for d in $(SUBDIRS); do make DESTDIR=`cd $(DESTDIR); pwd` -C $$d install; [ $$? = 0 ] || exit 1; done $(NAME)-$(VERSION).tar.gz: clean gpgkey - @mkdir -p $(RPM_BUILD_DIR) - @svn export --force $(PWD) $(RPM_BUILD_DIR)/$(NAME)-$(VERSION) - @mkdir -p $(RPM_BUILD_DIR)/$(NAME)-$(VERSION)/gpgkeys - @cp gpgkeys/rhsupport.pub $(RPM_BUILD_DIR)/$(NAME)-$(VERSION)/gpgkeys/. + @mkdir -p $(ARCHIVE_DIR) + @tar -cv sosreport sos doc man po sos.conf TODO LICENSE README sos.spec Makefile | tar -x -C $(ARCHIVE_DIR) + @mkdir -p $(ARCHIVE_DIR)/gpgkeys + @cp gpgkeys/rhsupport.pub $(ARCHIVE_DIR)/gpgkeys/. @tar Ccvzf $(RPM_BUILD_DIR) $(RPM_BUILD_DIR)/$(NAME)-$(VERSION).tar.gz $(NAME)-$(VERSION) clean: @@ -65,3 +75,27 @@ gpgkey: @echo "Building gpg key" @test -f gpgkeys/rhsupport.pub && echo "GPG key already exists." || \ gpg --batch --gen-key gpgkeys/gpg.template + +po: clean + mkdir -p $(PO_DIR) + for po in `ls po/*.po`; do \ + $(MSGCAT) -p -o $(PO_DIR)/$$(basename $$po | awk -F. '{print $$1}').properties $$po; \ + done; \ + + cp $(PO_DIR)/en.properties $(PO_DIR)/en_US.properties + +eap6: po + cp -r sos/* $(SRC_BUILD)/sos/ + find $(SRC_BUILD)/sos/plugins/ -not -name "*eap6.py" -not -name "*__init__.py" -type f -delete + +zip: po + zip -r $(ZIP_DEST) sos + zip -r $(ZIP_DEST) __run__.py + cd $(SRC_BUILD) && zip -r $(ARCHIVE_NAME) sos + cd $(SRC_BUILD) && rm -rf sos + +test: + @for test in `ls tests/*test*.py`; do \ + echo $$test; \ + PYTHONPATH=`pwd` python $$test; \ + done; \ @@ -1,35 +1,15 @@ -This set of tools is designed to provide information to support -organizations in an extensible manner, allowing third parties, -package maintainers, and anyone else to provide plugins that will -collect, analyze, and report information that is useful for supporting -software packages. +This set of tools is designed to provide information to support organizations +in an extensible manner, allowing third parties, package maintainers, and +anyone else to provide plugins that will collect, analyze, and report +information that is useful for supporting software packages. -This project is hosted at http://fedorahosted.org/sos -For the latest version, to contribute, and for more information, please visit there. +This project is hosted at http://github.com/sosreport/sosreport For the latest +version, to contribute, and for more information, please visit there. To access to the public source code repository for this project run: - svn export http://svn.fedorahosted.org/svn/sos/trunk sos --username guest + git clone git://github.com/sosreport/sosreport.git to install locally (as root) ==> make install to build an rpm ==> make rpm - -See the Makefile. - -Maintainer: - - Adam Stokes <ajs@redhat.com> - -Developers and Contributors: - - Steve Conklin <sconklin@redhat.com> - Pierre Amadio <pamadio@redhat.com> - John Berninger <jwb@redhat.com> - Navid Sheikhol-Eslami <navid at redhat dot com> - -Thanks to: - - Eva Schaller <eschaller@redhat.com> for providing an Italian translation - Marco Ceci <mceci@redhat.com> for helping me out with the cluster plugin - Leonardo Macchia <lmacchia@redhat.com> for being my personal regexp generator - Imed Chihi <ichihi@redhat.com> for providing Arabic and French translations +to build a zipfile for use with jython ==> make zip diff --git a/__run__.py b/__run__.py new file mode 100755 index 00000000..663ee144 --- /dev/null +++ b/__run__.py @@ -0,0 +1,17 @@ +# 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. +from sos.sosreport import main +import sys + +main(sys.argv[1:]) @@ -46,7 +46,7 @@ rm -rf ${RPM_BUILD_ROOT} %{python_sitelib}/* %{_mandir}/man1/* %{_mandir}/man5/* -%doc README TODO LICENSE ChangeLog doc/* +%doc README TODO LICENSE doc/* %config(noreplace) %{_sysconfdir}/sos.conf %changelog diff --git a/sos/__init__.py.in b/sos/__init__.py index 64b87506..8d45216a 100644 --- a/sos/__init__.py.in +++ b/sos/__init__.py @@ -15,13 +15,24 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import gettext +__version__ = "@SOSVERSION@" -gettext_dir = "/usr/share/locale" -gettext_app = "sos" +try: + from java.util import ResourceBundle -gettext.bindtextdomain(gettext_app, gettext_dir) + rb = ResourceBundle.getBundle("sos.po.sos") -__version__="@SOSVERSION@" -def _sos(msg): - return gettext.dgettext(gettext_app, msg) + def _sos(msg): + try: + return rb.getString(msg).encode('utf-8') + except: + return msg +except: + import gettext + gettext_dir = "/usr/share/locale" + gettext_app = "sos" + + gettext.bindtextdomain(gettext_app, gettext_dir) + + def _sos(msg): + return gettext.dgettext(gettext_app, msg) diff --git a/sos/helpers.py b/sos/helpers.py deleted file mode 100755 index b6b099a5..00000000 --- a/sos/helpers.py +++ /dev/null @@ -1,79 +0,0 @@ -## helpers.py -## Implement policies required for the sos system support tool - -## 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. - -## Some code adapted from "Python Cookbook, 2nd ed", by Alex -## Martelli, Anna Martelli Ravenscroft, and David Ascher -## (O'Reilly Media, 2005) 0-596-00797-3 -## - -""" -helper functions used by sosreport and plugins -""" -import os, sys -import logging -from subprocess import Popen, PIPE - -def importPlugin(pluginname, name): - """ Import a plugin to extend capabilities of sosreport - """ - try: - plugin = __import__(pluginname, globals(), locals(), [name]) - except ImportError: - return None - return getattr(plugin, name) - -def sosGetCommandOutput(command, timeout = 300): - """ Execute a command and gather stdin, stdout, and return status. - """ - # soslog = logging.getLogger('sos') - # Log if binary is not runnable or does not exist - for path in os.environ["PATH"].split(":"): - cmdfile = command.strip("(").split()[0] - # handle both absolute or relative paths - if ( ( not os.path.isabs(cmdfile) and os.access(os.path.join(path,cmdfile), os.X_OK) ) or \ - ( os.path.isabs(cmdfile) and os.access(cmdfile, os.X_OK) ) ): - break - else: - # soslog.log(logging.VERBOSE, "binary '%s' does not exist or is not runnable" % cmdfile) - return (127, "", 0) - - p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, bufsize=-1) - stdout, stderr = p.communicate() - return (p.returncode, stdout.strip(), 0) - -def commonPrefix(l1, l2, common = []): - ''' return a list of common elements at the start of all sequences, - then a list of lists that are the unique tails of each sequence. ''' - if len(l1) < 1 or len(l2) < 1 or l1[0] != l2[0]: return common, [l1, l2] - return commonPrefix(l1[1:], l2[1:], common+[l1[0]]) - -def sosRelPath(path1, path2, sep=os.path.sep, pardir=os.path.pardir): - ''' return a relative path from path1 equivalent to path path2. - In particular: the empty string, if path1 == path2; - path2, if path1 and path2 have no common prefix. - ''' - try: - common, (u1, u2) = commonPrefix(path1.split(sep), path2.split(sep)) - except AttributeError: - return path2 - - if not common: - return path2 # leave path absolute if nothing at all in common - return sep.join( [pardir]*len(u1) + u2 ) - diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py index e69de29b..ebb76ea3 100644 --- a/sos/plugins/__init__.py +++ b/sos/plugins/__init__.py @@ -0,0 +1,615 @@ +## 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 + +from sos.utilities import sosGetCommandOutput, import_module +from sos import _sos as _ +import inspect +import os +import sys +import string +import glob +import re +import traceback +import shutil +from stat import * +from time import time +from itertools import * +from collections import deque +import logging + + +def commonPrefix(l1, l2, common = None): + """ + Returns a tuple like the following: + ([common, elements, from l1, and l2], [[tails, from, l1], [tails, from, l2]]) + + >>> commonPrefix(['usr','share','foo'], ['usr','share','bar']) + (['usr','share'], [['foo'], ['bar']]) + """ + if common is None: + common = [] + if len(l1) < 1 or len(l2) < 1 or l1[0] != l2[0]: + return (common, [l1, l2]) + return commonPrefix(l1[1:], l2[1:], common+[l1[0]]) + +def sosRelPath(path1, path2, sep=os.path.sep, pardir=os.path.pardir): + ''' return a relative path from path1 equivalent to path path2. + In particular: the empty string, if path1 == path2; + path2, if path1 and path2 have no common prefix. + ''' + try: + common, (u1, u2) = commonPrefix(path1.split(sep), path2.split(sep)) + except AttributeError: + return path2 + + if not common: + return path2 # leave path absolute if nothing at all in common + return sep.join( [pardir]*len(u1) + u2 ) + + +class PluginException(Exception): + pass + + +class Plugin(object): + """ + This is the base class for sosreport plugins. This class should + be subclassed by platform specific superclasses. Actual plugins + should not subclass this class directly. + """ + + requires_root = True + version = 'unversioned' + + def __init__(self, commons): + if not getattr(self, "optionList", False): + self.optionList = deque() + + self.copiedFiles = deque() + self.executedCommands = deque() + self.diagnose_msgs = deque() + self.alerts = deque() + self.customText = "" + self.optNames = deque() + self.optParms = deque() + self.cInfo = commons + self.forbiddenPaths = deque() + self.copyPaths = deque() + self.copyStrings = deque() + self.collectProgs = deque() + + self.packages = deque() + self.files = deque() + + self.must_exit = False + + self.soslog = logging.getLogger('sos') + self.proflog = logging.getLogger('sosprofile') + + # 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]}) + + @classmethod + def name(class_): + "Returns the plugin's name as a string" + return class_.__name__.lower() + + def setArchive(self, archive): + self.archive = archive + + def policy(self): + return self.cInfo["policy"] + + def isInstalled(self, package_name): + '''Is the package $package_name installed? + ''' + return (self.policy().pkgByName(package_name) is not None) + + def doRegexSub(self, srcpath, regexp, subst): + '''Apply a regexp substitution to a file archived by sosreport. + srcpath is the path in the archive where the file can be found. + regexp can be a regexp string or a compiled re object. + subst is a string to replace each occurance of regexp in the content + of srcpath. + + This function returns the number of replacements made. + ''' + try: + path = self._get_dest_for_srcpath(srcpath) + if not path: + return 0 + readable = self.archive.open_file(path) + result, replacements = re.subn(regexp, subst, readable.read()) + if replacements: + self.archive.add_string(result, srcpath) + return replacements + else: + return 0 + except Exception: + return 0 + + def doRegexFindAll(self, regex, fname): + ''' Return a list of all non overlapping matches in the string(s) + ''' + try: + return re.findall(regex, open(fname, 'r').read(), re.MULTILINE) + except: # IOError, AttributeError, etc. + return [] + + def _path_in_path_list(self, path, path_list): + for p in path_list: + if p in path: + return True + return False + + def copy_symlink(self, srcpath, sub=None): + link = os.readlink(srcpath) + if not os.path.isabs(link): + link = os.path.normpath( + os.path.join( + os.path.dirname(srcpath), + link) + ) + + if os.path.isdir(link): + self.soslog.debug("link %s is a directory, skipping..." % link) + return + + dest = link + + if sub: + old, new = sub + dest = srcpath.replace(old, new) + + self.archive.add_file(link, dest=dest) + + self.copiedFiles.append({ + 'srcpath':srcpath, + 'dstpath':dest, + 'symlink':"yes", + 'pointsto':link}) + + def copy_dir(self, srcpath, sub=None): + for afile in os.listdir(srcpath): + self.doCopyFileOrDir(os.path.join(srcpath, afile), dest=None, sub=sub) + + def _get_dest_for_srcpath(self, srcpath): + for copied in self.copiedFiles: + if srcpath == copied["srcpath"]: + return copied["dstpath"] + return None + + # Methods for copying files and shelling out + def doCopyFileOrDir(self, srcpath, dest=None, sub=None): + # 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. sub can be used to + rename the destination of the file, sub should be a two-tuple of + (old,new). For example if you passed in ("etc","configurations") for + use against /etc/my_file.conf the file would end up at + /configurations/my_file.conf. + ''' + + if self.cInfo['cmdlineopts'].profiler: + start_time = time() + + if self._path_in_path_list(srcpath, self.forbiddenPaths): + self.soslog.debug("%s is in the forbidden path list" % srcpath) + return '' + + if not os.path.exists(srcpath): + self.soslog.debug("file or directory %s does not exist" % srcpath) + return + + if not dest: + dest = srcpath + + if sub: + old, new = sub + dest = srcpath.replace(old, new) + + if os.path.islink(srcpath): + self.copy_symlink(srcpath, sub=sub) + return + else: + if os.path.isdir(srcpath): + self.copy_dir(srcpath, sub=sub) + return + + # if we get here, it's definitely a regular file (not a symlink or dir) + self.soslog.debug("copying file %s to %s" % (srcpath,dest)) + + try: + self.archive.add_file(srcpath, dest) + + self.copiedFiles.append({ + 'srcpath':srcpath, + 'dstpath':dest, + 'symlink':"no"}) + + if self.cInfo['cmdlineopts'].profiler: + time_passed = time() - start_time + self.proflog.debug("copied: %-75s time: %f" % (srcpath, time_passed)) + except Exception, e: + self.soslog.debug(traceback.format_exc()) + + + 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, value): + ''' set the named option to value. + ''' + for name, parms in izip(self.optNames, self.optParms): + if name == optionname: + parms['enabled'] = value + return True + else: + return False + + def isOptionEnabled(self, optionname): + ''' Deprecated, use getOption() instead + ''' + return self.getOption(optionname) + + def getOption(self, optionname, default=0): + """Returns the first value that matches 'optionname' in parameters + passed in via the command line or set via set_option or via the + global_plugin_options dictionary, in that order. + + optionaname may be iterable, in which case the first option that matches + any of the option names is returned.""" + + def _check(key): + if hasattr(key, "__iter__"): + return key in optionname + else: + return key == optionname + + for name, parms in izip(self.optNames, self.optParms): + if _check(name): + return parms['enabled'] + + for key, value in self.cInfo.get('global_plugin_options', {}).iteritems(): + if _check(key): + return value + + return default + + def getOptionAsList(self, optionname, delimiter=",", default=None): + '''Will try to return the option as a list separated by the delimiter''' + option = self.getOption(optionname) + try: + opt_list = [opt.strip() for opt in option.split(delimiter)] + return filter(None, opt_list) + except Exception: + return default + + def addCopySpecLimit(self, fname, sizelimit=None, sub=None): + """Add a file specification (with limits) + """ + if not ( fname and len(fname) ): + # self.soslog.warning("invalid file path") + return False + files = glob.glob(fname) + 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, sub) + # Truncate the first file (others would likely be compressed), + # ensuring we get at least some logs + # FIXME: figure this out for jython + if flog == files[0] and limit_reached: + self.collectExtOutput("tail -c%d %s" % (sizelimit, flog), + "tail_" + os.path.basename(flog), flog[1:] + ".tailed") + + def addCopySpecs(self, copyspecs, sub=None): + for copyspec in copyspecs: + self.addCopySpec(copyspec, sub) + + def addCopySpec(self, copyspec, sub=None): + """ Add a file specification (can be file, dir,or shell glob) to be + copied into the sosreport by this module + """ + if not (copyspec and len(copyspec)): + # self.soslog.warning("invalid file path") + return False + # Glob case handling is such that a valid non-glob is a reduced glob + for filespec in glob.glob(copyspec): + if filespec not in self.copyPaths: + self.copyPaths.append((filespec, sub)) + + def callExtProg(self, prog): + """ Execute a command independantly of the output gathering part of + sosreport + """ + # pylint: disable-msg = W0612 + status, shout, runtime = sosGetCommandOutput(prog) + return (status, shout, runtime) + + def checkExtprog(self, prog): + """ Execute a command independently of the output gathering part of + sosreport and check the return code. Return True for a return code of 0 + and False otherwise.""" + (status, output, runtime) = self.callExtProg(prog) + return (status == 0) + + + def collectExtOutput(self, exe, suggest_filename = None, root_symlink = None, timeout = 300): + """ + Run a program and collect the output + """ + self.collectProgs.append( (exe, suggest_filename, root_symlink, timeout) ) + + def fileGrep(self, regexp, fname): + try: + return [l for l in open(fname).readlines() if re.match(regexp, l)] + except: # IOError, AttributeError, etc. + return [] + + 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 = os.path.join(self.cInfo['cmddir'], self.name(), 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 addStringAsFile(self, content, filename): + """Add a string to the archive as a file named `filename`""" + self.copyStrings.append((content, filename)) + + def collectOutputNow(self, exe, suggest_filename=None, root_symlink=False, timeout=300): + """ Execute a command and save the output to a file for inclusion in + the report + """ + if self.cInfo['cmdlineopts'].profiler: + start_time = time() + + # pylint: disable-msg = W0612 + status, shout, runtime = sosGetCommandOutput(exe, timeout=timeout) + + if suggest_filename: + outfn = self.makeCommandFilename(suggest_filename) + else: + outfn = self.makeCommandFilename(exe) + + if not (status == 127 or status == 32512): # if not command_not_found + outfn_strip = outfn[len(self.cInfo['cmddir'])+1:] + self.archive.add_string(shout, outfn) + else: + self.soslog.debug("could not run command: %s" % exe) + outfn = None + outfn_strip = None + + # 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) + + if self.cInfo['cmdlineopts'].profiler: + time_passed = time() - start_time + self.proflog.debug("output: %-75s time: %f" % (exe, time_passed)) + + 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) + + # 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) + + 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 += text + + def copyStuff(self): + """ + Collect the data for a plugin + """ + for path, sub in self.copyPaths: + self.doCopyFileOrDir(path, sub=sub) + + for string, file_name in self.copyStrings: + try: + self.archive.add_string(string, + os.path.join('sos_strings', self.name(), file_name)) + except Exception, e: + self.soslog.debug("could not create %s, traceback follows: %s" % (file_name, e)) + + for progs in izip(self.collectProgs): + prog, suggest_filename, root_symlink, timeout = progs[0] + # self.soslog.debug("collecting output of '%s'" % prog) + try: + self.collectOutputNow(prog, suggest_filename, root_symlink, timeout) + except Exception, e: + self.soslog.debug("error collection output of '%s', traceback follows: %s" % (prog, e)) + + def exit_please(self): + """ This function tells the plugin that it should exit ASAP""" + self.must_exit = True + + 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. + """ + # some files or packages have been specified for this package + if len(self.files) or len(self.packages): + for fname in self.files: + if os.path.exists(fname): + return True + for pkgname in self.packages: + if self.isInstalled(pkgname): + return True + return False + + 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 diagnose(self): + """This method must be overridden to check the sanity of the system's + configuration before the collection begins. + """ + pass + + def setup(self): + """This method 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.name() + + # Intro + html = html + "<h2> Plugin <em>" + self.name() + "</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" + + # 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 + + +class RedHatPlugin(object): + """Tagging class to indicate that this plugin works with Red Hat Linux""" + pass + +class IndependentPlugin(object): + """Tagging class that indicates this plugin can run on any platform""" + pass + +def import_plugin(name): + """Import name as a module and return a list of all classes defined in that + module""" + try: + plugin_fqname = "sos.plugins.%s" % name + return import_module(plugin_fqname, superclass=Plugin) + except ImportError, e: + return None diff --git a/sos/plugins/abrt.py b/sos/plugins/abrt.py index 22637a87..3ab7584c 100644 --- a/sos/plugins/abrt.py +++ b/sos/plugins/abrt.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class abrt(sos.plugintools.PluginBase): +class abrt(Plugin, RedHatPlugin): """ABRT log dump """ @@ -26,7 +26,7 @@ class abrt(sos.plugintools.PluginBase): def checkenabled(self): return self.isInstalled("abrt-cli") or \ exists("/var/spool/abrt") - + def do_backtraces(self): ret, output, rtime = self.callExtProg('/usr/bin/sqlite3 /var/spool/abrt/abrt-db \'select UUID from abrt_v4\'') try: diff --git a/sos/plugins/acpid.py b/sos/plugins/acpid.py index 24ec432c..bc335eea 100644 --- a/sos/plugins/acpid.py +++ b/sos/plugins/acpid.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class acpid(sos.plugintools.PluginBase): +class acpid(Plugin, RedHatPlugin): """acpid related information """ def setup(self): diff --git a/sos/plugins/amd.py b/sos/plugins/amd.py index c0fd930b..2a6776c6 100644 --- a/sos/plugins/amd.py +++ b/sos/plugins/amd.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class amd(sos.plugintools.PluginBase): +class amd(Plugin, RedHatPlugin): """Amd automounter information """ diff --git a/sos/plugins/anaconda.py b/sos/plugins/anaconda.py index aa7d4d1f..de7787bd 100644 --- a/sos/plugins/anaconda.py +++ b/sos/plugins/anaconda.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class anaconda(sos.plugintools.PluginBase): +class anaconda(Plugin, RedHatPlugin): """Anaconda / Installation information """ def checkenabled(self): diff --git a/sos/plugins/apache.py b/sos/plugins/apache.py index 4a41de11..a85f56ac 100644 --- a/sos/plugins/apache.py +++ b/sos/plugins/apache.py @@ -12,13 +12,13 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class apache(sos.plugintools.PluginBase): +class apache(Plugin, RedHatPlugin): """Apache related information """ optionList = [("log", "gathers all apache logs", "slow", False)] - + def setup(self): self.addCopySpecs([ "/etc/httpd/conf/httpd.conf", diff --git a/sos/plugins/auditd.py b/sos/plugins/auditd.py index c76bf5f8..8ccae7f7 100644 --- a/sos/plugins/auditd.py +++ b/sos/plugins/auditd.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class auditd(sos.plugintools.PluginBase): +class auditd(Plugin, RedHatPlugin): """Auditd related information """ diff --git a/sos/plugins/autofs.py b/sos/plugins/autofs.py index 84de4497..a295b23e 100644 --- a/sos/plugins/autofs.py +++ b/sos/plugins/autofs.py @@ -14,17 +14,17 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os, re -class autofs(sos.plugintools.PluginBase): +class autofs(Plugin, RedHatPlugin): """autofs server-related information """ def checkenabled(self): self.packages = [ "autofs" ] self.files = [ "/etc/sysconfig/autofs" ] - return sos.plugintools.PluginBase.checkenabled(self) - + return Plugin.checkenabled(self) + def checkdebug(self): """ testing if autofs debug has been enabled anywhere """ @@ -35,14 +35,14 @@ class autofs(sos.plugintools.PluginBase): if opt2 in ("--debug", "debug"): return True return False - + def getdaemondebug(self): """ capture daemon debug output """ debugout = self.fileGrep(r"^(daemon.*)\s+(\/var\/log\/.*)", "/etc/sysconfig/autofs") for i in debugout: return i[1] - + def setup(self): self.addCopySpec("/etc/auto*") self.collectExtOutput("/bin/rpm -qV autofs") diff --git a/sos/plugins/bootloader.py b/sos/plugins/bootloader.py index 49f9bcf3..b0df9a9f 100644 --- a/sos/plugins/bootloader.py +++ b/sos/plugins/bootloader.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class bootloader(sos.plugintools.PluginBase): +class bootloader(Plugin, RedHatPlugin): """Bootloader information """ def setup(self): diff --git a/sos/plugins/cluster.py b/sos/plugins/cluster.py index 45bb23e6..2c61380c 100644 --- a/sos/plugins/cluster.py +++ b/sos/plugins/cluster.py @@ -12,11 +12,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os, re from glob import glob -class cluster(sos.plugintools.PluginBase): +class cluster(Plugin, RedHatPlugin): """cluster suite and GFS related information """ @@ -26,7 +26,7 @@ class cluster(sos.plugintools.PluginBase): def checkenabled(self): rhelver = self.policy().rhelVersion() if rhelver == 4: - self.packages = [ "ccs", "cman", "cman-kernel", "magma", "magma-plugins", + self.packages = [ "ccs", "cman", "cman-kernel", "magma", "magma-plugins", "rgmanager", "fence", "dlm", "dlm-kernel", "gulm", "GFS", "GFS-kernel", "lvm2-cluster" ] elif rhelver == 5: @@ -38,7 +38,7 @@ class cluster(sos.plugintools.PluginBase): "cman", "clusterlib", "fence-agents" ] self.files = [ "/etc/cluster/cluster.conf" ] - return sos.plugintools.PluginBase.checkenabled(self) + return Plugin.checkenabled(self) def setup(self): rhelver = self.policy().rhelVersion() @@ -76,10 +76,10 @@ class cluster(sos.plugintools.PluginBase): if rhelver is 4: self.addCopySpec("/proc/cluster/*") self.collectExtOutput("cman_tool nodes") - + if rhelver is not 4: # 5+ self.collectExtOutput("cman_tool -a nodes") - + if rhelver is 5: self.collectExtOutput("group_tool -v") self.collectExtOutput("group_tool dump fence") diff --git a/sos/plugins/cobbler.py b/sos/plugins/cobbler.py index c1a05147..d17b8020 100644 --- a/sos/plugins/cobbler.py +++ b/sos/plugins/cobbler.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class cobbler(sos.plugintools.PluginBase): +class cobbler(Plugin, RedHatPlugin): """cobbler related information """ def checkenabled(self): @@ -23,5 +23,5 @@ class cobbler(sos.plugintools.PluginBase): def setup(self): self.addCopySpec("/etc/cobbler") self.addCopySpec("/var/log/cobbler") - self.addCopySpec("/var/lib/rhn/kickstarts") + self.addCopySpec("/var/lib/rhn/kickstarts") self.addCopySpec("/var/lib/cobbler") diff --git a/sos/plugins/corosync.py b/sos/plugins/corosync.py index 9c1592f7..862659fe 100644 --- a/sos/plugins/corosync.py +++ b/sos/plugins/corosync.py @@ -12,18 +12,16 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools -import os, re -import time, libxml2 +from sos.plugins import Plugin, RedHatPlugin -class corosync(sos.plugintools.PluginBase): +class corosync(Plugin, RedHatPlugin): """ corosync information """ def checkenabled(self): self.files = ['/usr/sbin/corosync'] self.packages = ['corosync'] - return sos.plugintools.PluginBase.checkenabled(self) + return Plugin.checkenabled(self) def setup(self): self.addCopySpecs([ diff --git a/sos/plugins/crontab.py b/sos/plugins/crontab.py index 191b13f5..ae0a55d3 100644 --- a/sos/plugins/crontab.py +++ b/sos/plugins/crontab.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class crontab(sos.plugintools.PluginBase): +class crontab(Plugin, RedHatPlugin): """Crontab information """ def setup(self): diff --git a/sos/plugins/cs.py b/sos/plugins/cs.py index 3142892e..1d518117 100644 --- a/sos/plugins/cs.py +++ b/sos/plugins/cs.py @@ -16,11 +16,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists from glob import glob -class cs(sos.plugintools.PluginBase): +class cs(Plugin, RedHatPlugin): """Red Hat Certificate System 7.1, 7.3, 8.0 and dogtag related information """ diff --git a/sos/plugins/devicemapper.py b/sos/plugins/devicemapper.py index 03cb54fe..a345e5bc 100644 --- a/sos/plugins/devicemapper.py +++ b/sos/plugins/devicemapper.py @@ -12,11 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools import os -from sos.helpers import sosGetCommandOutput +from sos.plugins import Plugin, RedHatPlugin -class devicemapper(sos.plugintools.PluginBase): +class devicemapper(Plugin, RedHatPlugin): """device-mapper related information (dm, lvm, multipath) """ diff --git a/sos/plugins/dhcp.py b/sos/plugins/dhcp.py index 4eb185d7..ee6719a6 100644 --- a/sos/plugins/dhcp.py +++ b/sos/plugins/dhcp.py @@ -12,16 +12,16 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class dhcp(sos.plugintools.PluginBase): +class dhcp(Plugin, RedHatPlugin): """DHCP related information """ def checkenabled(self): self.files = ['/etc/rc.d/init.d/dhcpd'] self.packages = ['dhcp'] - return sos.plugintools.PluginBase.checkenabled(self) - + return Plugin.checkenabled(self) + def setup(self): self.addCopySpecs([ "/etc/dhcpd.conf", diff --git a/sos/plugins/dovecot.py b/sos/plugins/dovecot.py index 9c201cea..705088e2 100644 --- a/sos/plugins/dovecot.py +++ b/sos/plugins/dovecot.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class dovecot(sos.plugintools.PluginBase): +class dovecot(Plugin, RedHatPlugin): """dovecot server related information """ def setup(self): diff --git a/sos/plugins/ds.py b/sos/plugins/ds.py index ca8a4b2a..107274c8 100644 --- a/sos/plugins/ds.py +++ b/sos/plugins/ds.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class ds(sos.plugintools.PluginBase): +class ds(Plugin, RedHatPlugin): """Directory Server information """ diff --git a/sos/plugins/eap6.py b/sos/plugins/eap6.py new file mode 100644 index 00000000..2ac942f4 --- /dev/null +++ b/sos/plugins/eap6.py @@ -0,0 +1,339 @@ +import os +import re +import zipfile +import urllib2 + +try: + import json +except ImportError: + import simplejson as json + +from sos.plugins import Plugin, IndependentPlugin +from sos.utilities import DirTree, find, checksum + +class Request(object): + + def __init__(self, resource, operation="read-resource", parameters=None): + self.resource = resource + self.operation = operation + if parameters: + self.parameters = parameters + else: + self.parameters = {} + + def url_parts(self): + """Generator function to split a url into (key, value) tuples. The url + should contain an even number of pairs. In the case of / the generator + will immediately stop iteration.""" + parts = self.resource.strip("/").split("/") + + if parts == ['']: + raise StopIteration + + while parts: + yield (parts.pop(0), parts.pop(0)) + + +class EAP6(Plugin, IndependentPlugin): + """JBoss related information + """ + + requires_root = False + + version = "1.0" + + optionList = [ + ("home", "JBoss's installation dir (i.e. JBOSS_HOME)", '', False), + ("logsize", 'max size (MiB) to collect per log file', '', 15), + ("stdjar", 'Collect jar statistics for standard jars.', '', True), + ("host", 'hostname of the management api for jboss', '', 'localhost'), + ("port", 'port of the management api for jboss', '', '9990'), + ("user", 'username for management console', '', None), + ("pass", 'password for management console', '', None), + ("appxml", "comma separated list of application's whose XML descriptors you want. The keyword 'all' will collect all descriptors in the designated profile(s).", '', False), + ] + + __MD5_CHUNK_SIZE=128 + __jbossHome=None + __haveJava=False + __twiddleCmd=None + __jbossServerConfigDirs = ["standalone", "domain"] + __jbossHTMLBody=None + + def __alert(self, msg): + self.soslog.error(msg) + self.addAlert(msg) + + def __getJbossHome(self): + """ + Will attempt to locate the JBoss installation dir in either jboss.home or + scrape it from the environment variable JBOSS_HOME. + Returns: + True JBOSS_HOME is set and the path exists. False otherwise. + """ + + if self.getOption("home"): + ## Prefer this value first over the ENV + self.__jbossHome=self.getOption(("home", "as7_home")) + self.addAlert("INFO: The JBoss installation directory supplied to SOS is " + + self.__jbossHome) + elif os.environ.get("JBOSS_HOME"): + self.__jbossHome=os.environ.get("JBOSS_HOME") + self.addAlert("INFO: The JBoss installation directory (i.e. JBOSS_HOME) from the environment is " + + self.__jbossHome) + else: + self.addAlert("ERROR: The JBoss installation directory was not supplied.\ + The JBoss SOS plug-in cannot continue.") + return False + + return True + + def __getMd5(self, file): + """Returns the MD5 sum of the specified file.""" + + retVal = "?" * 32 + + try: + retVal = checksum(file, self.__MD5_CHUNK_SIZE) + except IOError, ioe: + self.__alert("ERROR: Unable to open %s for reading. Error: %s" % (file,ioe)) + + return retVal + + def __getManifest(self, jarFile): + """ + Given a jar file, this function will extract the Manifest and return it's contents + as a string. + """ + manifest = None + try: + zf = zipfile.ZipFile(jarFile) + try: + manifest = zf.read("META-INF/MANIFEST.MF") + except Exception, e: + self.__alert("ERROR: reading manifest from %s. Error: %s" % (jarFile, e)) + zf.close() + except Exception, e: + self.__alert("ERROR: reading contents of %s. Error: %s" % (jarFile, e)) + return manifest + + def __getStdJarInfo(self): + found = False + jar_info_list = [] + for jarFile in find("*.jar", self.__jbossHome): + checksum = self.__getMd5(jarFile) + manifest = self.__getManifest(jarFile) + path = jarFile.replace(self.__jbossHome, 'JBOSSHOME') + if manifest: + manifest = manifest.strip() + jar_info_list.append((path, checksum, manifest)) + found = True + if found: + jar_info_list.sort() + self.addStringAsFile("\n".join([ + "%s\n%s\n%s\n" % (name, checksum, manifest) + for (name, checksum, manifest) in jar_info_list]), + 'jarinfo.txt') + else: + self.addAlert("WARN: No jars found in JBoss system path (" + self.__jbossHome + ").") + + + def query(self, request_obj): + try: + return self.query_java(request_obj) + except Exception, e: + self.addAlert("JBOSS API call failed, falling back to HTTP: %s" % e) + return self.query_http(request_obj) + + def query_java(self, request_obj): + from org.jboss.dmr import ModelNode + controller_client = self.getOption('controller_client_proxy') + if not controller_client: + raise AttributeError("Controller Client is not available") + + request = ModelNode() + request.get("operation").set(request_obj.operation) + + for key, val in request_obj.url_parts(): + request.get('address').add(key,val) + + if request_obj.parameters: + for key, value in request_obj.parameters.iteritems(): + request.get(key).set(value) + + return controller_client.execute(request).toJSONString(True) + + def query_http(self, request_obj, postdata=None): + host = self.getOption(('host', 'as7_host')) + port = self.getOption(('port', 'as7_port')) + + username = self.getOption(('user', 'as7_user'), None) + password = self.getOption(('pass', 'as7_pass'), None) + + uri = "http://%s:%s/management" % (host,port) + + json_data = {'operation': request_obj.operation, + 'address': []} + + for key, val in request_obj.url_parts(): + json_data['address'].append({key:val}) + + for key, val in request_obj.parameters.iteritems(): + json_data[key] = val + + postdata = json.dumps(json_data) + headers = {'Content-Type': 'application/json', + 'Accept': 'application/json'} + + opener = urllib2.build_opener() + + if username and password: + passwd_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() + passwd_manager.add_password(realm="ManagementRealm", + uri=uri, + user=username, + passwd=password) + digest_auth_handler = urllib2.HTTPDigestAuthHandler(passwd_manager) + basic_auth_handler = urllib2.HTTPBasicAuthHandler(passwd_manager) + + opener.add_handler(digest_auth_handler) + opener.add_handler(basic_auth_handler) + + req = urllib2.Request(uri, data=postdata, headers=headers) + + try: + resp = opener.open(req) + return resp.read() + except Exception, e: + err_msg = "Could not query url: %s; error: %s" % (uri, e) + self.addAlert(err_msg) + return err_msg + + def _set_domain_info(self, parameters=None): + """This function will add host controller and server instance + name data if it is present to the desired resource. This is to support + domain-mode operation in AS7""" + host_controller_name = self.getOption("as7_host_controller_name") + server_name = self.getOption("as7_server_name") + + if host_controller_name and server_name: + if not parameters: + parameters = {} + + parameters['host'] = host_controller_name + parameters['server'] = server_name + + return parameters + + + def _resource_to_file(self, resource=None, parameters=None, operation='read-resource', outfile=None): + parameters = self._set_domain_info(parameters) + + r = Request(resource=resource, + parameters=parameters, + operation=operation) + self.addStringAsFile(self.query(r), filename=outfile) + + + def get_online_data(self): + """ + This function co-locates calls to the management api that gather + information from a running system. + """ + self._resource_to_file(resource="/", + parameters={"recursive": "true"}, + outfile="configuration.json") + self._resource_to_file(resource="/core-service/service-container", + operation="dump-services", + outfile="dump-services.json") + self._resource_to_file(resource="/subsystem/modcluster", + operation="read-proxies-configuration", + outfile="cluster-proxies-configuration.json") + self._resource_to_file(resource="/core-service/platform-mbean/type/threading", + operation="dump-all-threads", + parameters={"locked-synchronizers": "true", + "locked-monitors": "true"}, + outfile="threaddump.json") + + def __getFiles(self, configDirAry): + """ + This function will collect files from JBOSS_HOME for analysis. The scope of files to + be collected are determined by options to this SOS plug-in. + """ + + for dir_ in configDirAry: + path = os.path.join(self.__jbossHome, dir_) + ## First add forbidden files + self.addForbiddenPath(os.path.join(path, "tmp")) + self.addForbiddenPath(os.path.join(path, "work")) + self.addForbiddenPath(os.path.join(path, "data")) + + if os.path.exists(path): + ## First get everything in the conf dir + confDir = os.path.join(path, "configuration") + self.addForbiddenPath(os.path.join(confDir, 'mgmt-users.properties')) + + self.doCopyFileOrDir(confDir, sub=(self.__jbossHome, 'JBOSSHOME')) + ## Log dir next + logDir = os.path.join(path, "log") + + for logFile in find("*", logDir): + self.addCopySpecLimit(logFile, + self.getOption("logsize"), + sub=(self.__jbossHome, 'JBOSSHOME')) + + ## Deploy dir + deployDir = os.path.join(path, "deployments") + + for deployFile in find("*", deployDir, max_depth=1): + self.addCopySpec(deployFile, sub=(self.__jbossHome, 'JBOSSHOME')) + + def setup(self): + + ## We need to know where JBoss is installed and if we can't find it we + ## must exit immediately. + if not self.__getJbossHome(): + self.exit_please() + + try: + self.get_online_data() + except urllib2.URLError: + pass + + ## Generate hashes of the stock Jar files for the report. + if self.getOption("stdjar"): + self.__getStdJarInfo() + + ## Generate a Tree for JBOSS_HOME + tree = DirTree(self.__jbossHome).as_string() + self.addStringAsFile(tree, "jboss_home_tree.txt") + + self.__getFiles(self.__jbossServerConfigDirs) + + # FIXME: this is still not right, tweak the search paths to pick up the right files + def postproc(self): + """ + Obfuscate passwords. + """ + + password_xml_regex = re.compile(r'<password>.*</password>', re.IGNORECASE) + + for dir_ in self.__jbossServerConfigDirs: + path = os.path.join(self.__jbossHome, dir_) + + self.doRegexSub(os.path.join(path,"configuration","*.xml"), + password_xml_regex, + r'<password>********</password>') + + tmp = os.path.join(path,"configuration") + for propFile in find("*-users.properties", tmp): + self.doRegexSub(propFile, + r"=(.*)", + r'=********') + +# Remove PW from -ds.xml files + tmp = os.path.join(path, "deployments") + for dsFile in find("*-ds.xml", tmp): + self.doRegexSub(dsFile, + password_xml_regex, + r"<password>********</password>") diff --git a/sos/plugins/emc.py b/sos/plugins/emc.py index 843e7ef1..4d88e151 100644 --- a/sos/plugins/emc.py +++ b/sos/plugins/emc.py @@ -16,9 +16,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools, os +from sos.plugins import Plugin, RedHatPlugin, os -class emc(sos.plugintools.PluginBase): +class emc(Plugin, RedHatPlugin): """EMC related information (PowerPath, Solutions Enabler CLI and Navisphere CLI) """ @@ -146,7 +146,7 @@ class emc(sos.plugintools.PluginBase): def checkenabled(self): self.packages = [ "EMCpower" ] self.files = [ "/opt/Navisphere/bin", "/proc/emcp" ] - return sos.plugintools.PluginBase.checkenabled(self) + return Plugin.checkenabled(self) def setup(self): from subprocess import Popen, PIPE @@ -168,7 +168,7 @@ class emc(sos.plugintools.PluginBase): self.get_pp_config() ## If Solutions Enabler is installed collect Symmetrix/DMX specific information - if len(self.allPkgsByNameRegex('[Ss][Yy][Mm][Cc][Ll][Ii]-[Ss][Yy][Mm][Cc][Ll][Ii]')) > 0: + if len(self.policy().package_manager.allPkgsByNameRegex('[Ss][Yy][Mm][Cc][Ll][Ii]-[Ss][Yy][Mm][Cc][Ll][Ii]')) > 0: print "EMC Solutions Enabler SYMCLI is installed." print " Gathering EMC Solutions Enabler SYMCLI information..." self.addCustomText("EMC Solutions Enabler is installed.<br>") diff --git a/sos/plugins/filesys.py b/sos/plugins/filesys.py index baefaa32..25e46c50 100644 --- a/sos/plugins/filesys.py +++ b/sos/plugins/filesys.py @@ -12,12 +12,12 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os import re from itertools import * -class filesys(sos.plugintools.PluginBase): +class filesys(Plugin, RedHatPlugin): """information on filesystems """ optionList = [("lsof", 'gathers information on all open files', 'slow', False)] @@ -33,14 +33,14 @@ class filesys(sos.plugintools.PluginBase): "/etc/raidtab", "/etc/mdadm.conf"]) mounts = self.collectOutputNow("/bin/mount -l", root_symlink = "mount") - + self.collectExtOutput("/bin/findmnt") self.collectExtOutput("/bin/df -al", root_symlink = "df") self.collectExtOutput("/bin/df -ali") if self.getOption('lsof'): self.collectExtOutput("/usr/sbin/lsof -b +M -n -l -P", root_symlink = "lsof") self.collectExtOutput("/sbin/blkid -c /dev/null") - + part_titlep = re.compile("^major") blankp = re.compile("^$") partlist = [] @@ -68,7 +68,7 @@ class filesys(sos.plugintools.PluginBase): if bool(part_in_disk.match(dev)): devlist.append(dev) - for i in devlist: + for i in devlist: self.collectExtOutput("/sbin/parted -s %s print" % (i)) if self.getOption('dumpe2fs'): diff --git a/sos/plugins/ftp.py b/sos/plugins/ftp.py index e5914e78..3a7d2647 100644 --- a/sos/plugins/ftp.py +++ b/sos/plugins/ftp.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class ftp(sos.plugintools.PluginBase): +class ftp(Plugin, RedHatPlugin): """FTP server related information """ diff --git a/sos/plugins/gdm.py b/sos/plugins/gdm.py index 62207c0e..63cae615 100644 --- a/sos/plugins/gdm.py +++ b/sos/plugins/gdm.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class gdm(sos.plugintools.PluginBase): +class gdm(Plugin, RedHatPlugin): """gdm related information """ def setup(self): diff --git a/sos/plugins/general.py b/sos/plugins/general.py index 52c671d7..92244ceb 100644 --- a/sos/plugins/general.py +++ b/sos/plugins/general.py @@ -13,10 +13,10 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import os -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import commands -class general(sos.plugintools.PluginBase): +class general(Plugin, RedHatPlugin): """basic system information """ diff --git a/sos/plugins/gluster.py b/sos/plugins/gluster.py index bf5b25c7..24199512 100644 --- a/sos/plugins/gluster.py +++ b/sos/plugins/gluster.py @@ -13,9 +13,9 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import os.path -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class gluster(sos.plugintools.PluginBase): +class gluster(Plugin, RedHatPlugin): '''gluster related information''' def checkenabled(self): diff --git a/sos/plugins/hardware.py b/sos/plugins/hardware.py index 230100ca..631701f4 100644 --- a/sos/plugins/hardware.py +++ b/sos/plugins/hardware.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from glob import glob -class hardware(sos.plugintools.PluginBase): +class hardware(Plugin, RedHatPlugin): """hardware related information """ def setup(self): diff --git a/sos/plugins/hts.py b/sos/plugins/hts.py index 10af675d..7ab9cac7 100644 --- a/sos/plugins/hts.py +++ b/sos/plugins/hts.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class hts(sos.plugintools.PluginBase): +class hts(Plugin, RedHatPlugin): """Red Hat Hardware Test Suite related information """ def setup(self): diff --git a/sos/plugins/i18n.py b/sos/plugins/i18n.py index 8d4a26f7..2640f32d 100644 --- a/sos/plugins/i18n.py +++ b/sos/plugins/i18n.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class i18n(sos.plugintools.PluginBase): +class i18n(Plugin, RedHatPlugin): """i18n related information """ def setup(self): diff --git a/sos/plugins/initrd.py b/sos/plugins/initrd.py index 98d604a1..351e532d 100644 --- a/sos/plugins/initrd.py +++ b/sos/plugins/initrd.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from glob import glob -class initrd(sos.plugintools.PluginBase): +class initrd(Plugin, RedHatPlugin): """initrd related information """ def setup(self): diff --git a/sos/plugins/ipa.py b/sos/plugins/ipa.py index f315dcba..824526ad 100644 --- a/sos/plugins/ipa.py +++ b/sos/plugins/ipa.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class ipa(sos.plugintools.PluginBase): +class ipa(Plugin, RedHatPlugin): """IPA diagnostic information """ # ntp and dirserver stuff are covered in existing sos plugins, so we really only diff --git a/sos/plugins/ipsec.py b/sos/plugins/ipsec.py index 82c8a431..8fc09628 100644 --- a/sos/plugins/ipsec.py +++ b/sos/plugins/ipsec.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class ipsec(sos.plugintools.PluginBase): +class ipsec(Plugin, RedHatPlugin): """ipsec related information """ def checkenabled(self): diff --git a/sos/plugins/iscsi.py b/sos/plugins/iscsi.py index 26139c3c..56c5149b 100644 --- a/sos/plugins/iscsi.py +++ b/sos/plugins/iscsi.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class iscsi(sos.plugintools.PluginBase): +class iscsi(Plugin, RedHatPlugin): """iscsi-initiator related information """ def setup(self): diff --git a/sos/plugins/iscsitarget.py b/sos/plugins/iscsitarget.py index fddea77c..b12e7712 100644 --- a/sos/plugins/iscsitarget.py +++ b/sos/plugins/iscsitarget.py @@ -14,15 +14,15 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class iscsitarget(sos.plugintools.PluginBase): +class iscsitarget(Plugin, RedHatPlugin): """iscsi-target related information """ def checkenabled(self): self.packages = [ "scsi-target-utils" ] - return sos.plugintools.PluginBase.checkenabled(self) + return Plugin.checkenabled(self) def setup(self): self.addCopySpec("/etc/tgt/targets.conf") diff --git a/sos/plugins/jboss.py b/sos/plugins/jboss.py new file mode 100644 index 00000000..cffaf043 --- /dev/null +++ b/sos/plugins/jboss.py @@ -0,0 +1,708 @@ +import os +import zipfile +import platform +import fnmatch +import shlex +import subprocess +import string +import grp, pwd + +from sos.plugins import Plugin, RedHatPlugin +from sos.utilities import DirTree, find + +class jboss(Plugin, RedHatPlugin): + """JBoss related information + """ + + optionList = [("home", 'JBoss\'s installation dir (i.e. JBOSS_HOME)', '', False), + ("javahome", 'Java\'s installation dir (i.e. JAVA_HOME)', '', False), + ("profile", 'Quoted and space separated list of server profiles to limit collection. \ +Default=\'all default minimal production standard web\'.', '', False), + ("user", 'JBoss JMX invoker user to be used with twiddle.', '', False), + ("pass", 'JBoss JMX invoker user\'s password to be used with twiddle.', '', False), + ("logsize", 'max size (MiB) to collect per log file', '', 15), + ("stdjar", 'Collect jar statistics for standard jars.', '', True), + ("servjar", 'Collect jar statistics from any server configuration dirs.', '', True), + ("twiddle", 'Collect twiddle data.', '', True), + ("appxml", 'Quoted and space separated list of application\'s whose XML descriptors you want. The keyword \"all\" will collect all descriptors in the designated profile(s).', '', False)] + + __MD5_CHUNK_SIZE=128 + __jbossHome=None + __haveJava=False + __twiddleCmd=None + __jbossSystemJarDirs = [ "client", "lib" , "common/lib" ] + __jbossServerConfigDirs = ["all", "default", "minimal", "production", "standard", "web"] + __jbossHTMLBody=None + + def __getJbossHome(self): + """ + Will attempt to locate the JBoss installation dir in either jboss.home or + scrape it from the environment variable JBOSS_HOME. + Returns: + True JBOSS_HOME is set and the path exists. False otherwise. + """ + if self.getOption("home"): + ## Prefer this value first over the ENV + self.__jbossHome=self.getOption("home") + self.addAlert("INFO: The JBoss installation directory supplied to SOS is " + + self.__jbossHome) + elif os.environ.get("JBOSS_HOME"): + self.__jbossHome=os.environ.get("JBOSS_HOME") + self.addAlert("INFO: The JBoss installation directory (i.e. JBOSS_HOME) from the environment is " + + self.__jbossHome) + else: + self.addAlert("ERROR: The JBoss installation directory was not supplied.\ + The JBoss SOS plug-in cannot continue.") + return False + + if os.path.exists(self.__jbossHome): + ## We need to set JBOSS_CLASSPATH otherwise some twiddle commands will not work. + jbossClasspath=None + tmp=os.path.join(self.__jbossHome, "lib") + if os.path.exists(tmp): + jbossClasspath=tmp + os.sep + "*" + os.pathsep + else: + self.addAlert("WARN: The JBoss lib directory does not exist. Dir(%s) " % tmp) + + tmp=os.path.join(self.__jbossHome, "common" , "lib") + if os.path.exists(tmp): + jbossClasspath+=tmp + os.sep + "*" + else: + self.addAlert("WARN: The JBoss lib directory does not exist. Dir(%s) " % tmp) + + os.environ['JBOSS_CLASSPATH']=jbossClasspath + + return True + else: + msg = "ERROR: The path to the JBoss installation directory does not exist. Path is: " + self.__jbossHome + print msg + self.addAlert(msg) + return False + + def __getJavaHome(self): + """ + This SOS plug-in makes extensive use of JBoss' twiddle program and twiddle uses Java. As such, we + need to ensure that java and JAVA_HOME is known to the plug-in so that it can use Java. + This function will put JAVA_HOME and JAVA_HOME/bin into the environment if they're not already + there. + """ + javaHome=None + java="bin/java" + + if self.getOption("javahome"): + ## Prefer this value first over the ENV + javaHome=self.getOption("javahome") + self.addAlert("INFO: The Java installation directory supplied to SOS is " + + javaHome) + elif os.environ.get("JAVA_HOME"): + javaHome=os.environ.get("JAVA_HOME") + self.addAlert("INFO: The Java installation directory (i.e. JAVA_HOME) from the environment is " + + javaHome) + else: + ## Test to see if Java is already in the PATH + (status, output, rtime) = self.callExtProg("java -version") + if (status == 0): + self.addAlert("INFO: The Java installation directory is in the system path.") + return True + else: + self.addAlert("ERROR: The Java installation directory was not supplied.\ + The JBoss SOS plug-in will not collect twiddle data.") + return False + + + java=os.path.join(javaHome, java) + if os.path.exists(java) and os.access(java, os.X_OK): + os.environ['JAVA_HOME']=javaHome + ## Place the supplied Java at the *head* of the path. + os.environ['PATH'] = os.path.join(javaHome, "bin") + os.pathsep + os.environ['PATH'] + return True + else: + msg = "ERROR: The path to the Java installation directory does not exist. Path is: %s" % (javaHome) + print msg + self.addAlert(msg) + return False + + + def __getJMXCredentials(self): + """ + Read the JMX credentials from the option list. + Returns: + A formatted credential string for twiddle consumption if both user and pass + are supplied. None otherwise. + """ + credential = None + ## Let's make a best effort not to pass expansions or escapes to the shell + ## by strong quoting the user's input + if self.getOption("user"): + credential=" -u '" + self.getOption("user") + "' " + if self.getOption("pass"): + credential+=" -p '" + self.getOption("pass") + "' " + else: + credential=None + return credential + + def __updateServerConfigDirs(self): + """ + By default this plug-in will attempt to collect logs from every + JBoss server configuration directory (i.e. profile). The + user may have supplied a limited list, as such, we must respect + that wish. + Returns: + Nothing. Will update __jbossServerConfigDirs if the user + supplied a limited list. + """ + if self.getOption("profile"): + profiles=self.getOption("profile") + ## I'd rather use comma as the delimiter but getOption doesn't seem to be passing it through. + ## Since we are using spaces as the delimiter, we need to filter out empty list elements + ## if the user did something like ' all default web '. + profiles=profiles.split(' ') + ## Flter(None doesn't work. Allows 0. + self.__jbossServerConfigDirs=filter(lambda x: len(x), profiles) + return + + def __buildTwiddleCmd(self): + """ + Utility function to build the twiddle command with/without credentials + so that it can be used by later fcns. If twiddle is found + """ + ## In the off-chance that SOS is ever ported to cygwin or this plugin + ## is ported to win... + if platform.system() == "Windows": + self.__twiddleCmd=os.path.join(self.__jbossHome, "bin", "twiddle.bat") + else: + self.__twiddleCmd=os.path.join(self.__jbossHome, "bin", "twiddle.sh") + + if os.path.exists(self.__twiddleCmd) and os.access(self.__twiddleCmd, os.X_OK): + credential = self.__getJMXCredentials() + if credential: + self.__twiddleCmd += credential + else: + ## Reset twiddlecmd to None + self.addAlert("ERROR: The twiddle program could not be found. Program=%s" % (self.__twiddleCmd)) + self.__twiddleCmd = None + + return + + def __createHTMLBodyStart(self): + """ + The free-form HTML that can be inserted into the SOS report with addCustomText is within + a <p> block. We need to add a few pieces of HTML so that all of our subsequent data will + be rendered properly. + """ + self.__jbossHTMLBody = """ + <br/> + <br/> + <script type="text/javascript"> + <!-- + function show(h) { + var tbl = document.getElementById(h); + tbl.style.display = 'block'; + } + function hide(h) { + var tbl = document.getElementById(h); + tbl.style.display = 'none'; + } + // --> + </script> + <b>JBoss SOS Report Table of Contents</b> + <ul style="list-style-type: square"> + <li><a href="#system-jar-info">JBoss System Jar Information</a> + </li> + <li><a href="#profile-jar-info">JBoss Server Configurations Jar Information</a> + </li> + <li><a href="#jboss-home-directory-tree">JBOSS_HOME Directory Tree</a> + </li> + <li><a href="#jboss-system-mbean-data">JBoss JMX MBean Data from <tt>jboss.system:*</tt></a> + </li> + <li><a href="#jboss-mbean-data">JBoss JMX MBean Data from <tt>jboss:*</tt></a> + </li> + <li><a href="#jboss-mbean-summary">JBoss MBean Summary</a> + </li> + <li><a href="#jboss-messaging">JBoss JMX Messaging MBean Data from <tt>jboss.messaging:*</tt></a> + </li> + <li><a href="#jboss-j2ee">JBoss JMX J2EE MBean Data from <tt>jboss.j2ee:*</tt></a> + </li> + <li><a href="#jboss-vfs">JBoss JMX VFS MBean Data from <tt>jboss.vfs:*</tt></a> + </li> + <li><a href="#jboss-jsr77-data">JBoss JSR77 Data</a> + </li> + </ul> + <br/> + <br/> + """ + + def __getMd5(self, file): + """ + Will perform an MD5 sum on a given file and return the file's message digest. This function + will not read the entire file into memory, instead, it will consume the file in 128 byte + chunks. This might be slightly slower but, the intent of a SOS report is to collect data from + a system that could be under stress and we shouldn't stress it more by loading entire Jars into + real memory. + + Note: This fcn expects hashlib; however, this isn't always available. If it isn't then + we will use md5sum + """ + + retVal="????????????????????????????????" + + try: + import hashlib + try: + fd = open(file,"rb") + except IOError, ioe: + msg = "ERROR: Unable to open %s for reading. Error: " % (file,ioe) + print msg + self.addAlert(msg) + + md5 = hashlib.md5() + data = fd.read(self.__MD5_CHUNK_SIZE) + while data: + md5.update(data) + data = fd.read(self.__MD5_CHUNK_SIZE) + retVal = md5.hexdigest() + except ImportError, e: + process = subprocess.Popen(['md5sum', file], + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + result = process.communicate() + if (process.returncode == 0): + retVal = result[0].partition(' ')[0] + else: + msg = "ERROR: Unable to compute md5sum of %s. Msg (%s)" % (file, result[1]) + print msg + self.addAlert(msg) + + return retVal + + + def __getManifest(self, jarFile): + """ + Given a jar file, this function will extract the Manifest and return it's contents + as a string. + """ + manifest=None + try: + zf = zipfile.ZipFile(jarFile) + try: + manifest=zf.read("META-INF/MANIFEST.MF") + except Exception, e: + msg="ERROR: reading manifest from %s. Error: %s" % (jarFile, e) + print msg + self.addAlert(msg) + zf.close() + except Exception, e: + msg="ERROR: reading contents of %s. Error: %s" % (jarFile, e) + print msg + self.addAlert(msg) + return manifest + + def __getStdJarInfo(self): + + self.__jbossHTMLBody += """ + <div id="system-jar-info" style="font-weight: bold;">– JBoss System Jar Information</div> + """ + + for dir in self.__jbossSystemJarDirs: + path=os.path.join(self.__jbossHome, dir) + if os.path.exists(path): + nicePath=path.replace(os.sep, "-") + self.__jbossHTMLBody += """ + <div> + — Summary of Jar Files in JBoss System Directory + <tt>%s</tt> + ( <a href="javascript:show('%s')">Show</a> / <a + href="javascript:hide('%s')">Hide</a> ): + </div> + <div id="%s" style="overflow: hidden; display: none"> + <ul style="list-style-type: square"> + """ % (path,nicePath,nicePath,nicePath) + + found= False + for jarFile in find("*.jar", path): + found= True + nicePath=jarFile.replace(os.sep, "-") + self.__jbossHTMLBody += """ + <li>Jar File: <tt>%s</tt><br/> + MD5: <tt>%s</tt> + <br /> Manifest File ( + <a href="javascript:show('%s')">Show</a> / + <a href="javascript:hide('%s')">Hide</a> ):<br /> + <div id="%s" style="overflow: hidden; display: none"> + <pre> + %s + </pre> + </div> + </li> + """ % (jarFile, + self.__getMd5(jarFile), + nicePath, + nicePath, + nicePath, + self.__getManifest(jarFile)) + + if not found: + self.addAlert("WARN: No jars found in JBoss system path (" + path + ").") + self.__jbossHTMLBody += """ + </ul> + </div> + """ + else: + self.addAlert("ERROR: JBoss system path (" + path + ") does not exist.") + return + + def __getServerConfigJarInfo(self, configDirAry): + + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="profile-jar-info" style="font-weight: bold;">– JBoss Server Configurations Jar Information</div> + """ + for dir in configDirAry: + serverDir = os.path.join("server", dir) + path=os.path.join(self.__jbossHome, serverDir) + if os.path.exists(path): + nicePath=path.replace(os.sep, "-") + self.__jbossHTMLBody += """ + <div> + — Summary of Jar Files in the <tt>%s</tt> JBoss Server Configuration + ( <a href="javascript:show('%s')">Show</a> / <a + href="javascript:hide('%s')">Hide</a> ): + </div> + <div id="%s" style="overflow: hidden; display: none"> + <ul style="list-style-type: square"> + """ % (dir, nicePath,nicePath,nicePath) + + found = False + for jarFile in find("*.jar", path): + found = True + nicePath=jarFile.replace(os.sep, "-") + self.__jbossHTMLBody += """ + <li id="system-jar-info">Jar File: <tt>%s</tt><br/> + MD5: <tt>%s</tt> + <br /> Manifest File ( + <a href="javascript:show('%s')">Show</a> / + <a href="javascript:hide('%s')">Hide</a> ):<br /> + <div id="%s" style="overflow: hidden; display: none"> + <pre> + %s + </pre> + </div> + </li> + """ % (jarFile, + self.__getMd5(jarFile), + nicePath, + nicePath, + nicePath, + self.__getManifest(jarFile)) + + if not found: + self.addAlert("WARN: No jars found in the JBoss server configuration (%s)." % (path)) + + self.__jbossHTMLBody += """ + </ul> +</div> + """ + else: + self.addAlert("ERROR: JBoss server configuration path (" + path + ") does not exist.") + + return + + def __getJBossHomeTree(self): + """ + This function will execute the "tree" command on JBOSS_HOME. + """ + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="jboss-home-directory-tree" style="font-weight: bold;">– JBOSS_HOME Directory Tree</div> + + <div> + — JBOSS_HOME Tree + ( <a href="javascript:show('jboss-home-tree')">Show</a> / <a + href="javascript:hide('jboss-home-tree')">Hide</a> ): + </div> + <div id="jboss-home-tree" style="overflow: hidden; display: none"> + <pre> + """ + try: + output = DirTree(self.__jbossHome).as_string() + self.__jbossHTMLBody += """ +%s + </pre> + </div> + """ % (output) + except Exception, e: + self.__jbossHTMLBody += """ + ERROR: Unable to generate <tt>tree</tt> on JBOSS_HOME. + Exception: %s + </pre> + </div> + """ % e + return + + def __getMbeanData(self, dataTitle, divId, twiddleOpts): + credentials = "" + if self.__haveJava and self.__twiddleCmd: + self.__jbossHTMLBody += """ + <div> + — %s + ( <a href="javascript:show('%s')">Show</a> / <a + href="javascript:hide('%s')">Hide</a> ): + </div> + <div id="%s" style="overflow: hidden; display: none"> + <table style="margin-left: 30px;font-size:14px"> + <tr> + <td align="left"> + Twiddle Options: + </td> + <td align="left"><tt>%s</tt></td> + </tr> + </table> + <pre> + + """ % (dataTitle, divId, divId, divId,twiddleOpts) + cmd = "%s %s" % (self.__twiddleCmd, twiddleOpts) + + proc = subprocess.Popen(shlex.split(cmd), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + output = proc.communicate()[0] + status = proc.returncode + if status == 0 and output: + self.__jbossHTMLBody += output.strip() + else: + self.__jbossHTMLBody += """ + ERROR: Unable to collect %s data. + Output: %s + Status: %d + """ % (twiddleOpts, output, status) + else: + self.__jbossHTMLBody += "ERROR: Unable to collect data twiddle or Java is missing." + + self.__jbossHTMLBody += """ + </pre> + </div> + """ + return + + def __getTwiddleData(self): + """ + This function co-locates all of the calls to twiddle so that they can be easily disabled. + """ + + ## Get jboss.system.* Data + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="jboss-system-mbean-data" style="font-weight: bold;">– JBoss JMX MBean Data from <tt>jboss.system:*</tt></div> + """ + self.__getMbeanData("JBoss Server Info", + "jboss-server-info", + " get 'jboss.system:type=ServerInfo' ") + self.__getMbeanData("JBoss Server Config Info", + "jboss-server-config-info", + " get 'jboss.system:type=ServerConfig' ") + self.__getMbeanData("JBoss CXF Server Config Info", + "jboss-cxfserver-config-info", + " get 'jboss.ws:service=ServerConfig' ") + self.__getMbeanData("JBoss Memory Pool Info", + "jboss-memory-pool-info", + " invoke 'jboss.system:type=ServerInfo' listMemoryPools true ") + self.__getMbeanData("JBoss Thread CPU Utilization", + "jboss-thread-cpu-info", + " invoke 'jboss.system:type=ServerInfo' listThreadCpuUtilization ") + self.__getMbeanData("JBoss Thread Dump", + "jboss-thread-dump", + " invoke 'jboss.system:type=ServerInfo' listThreadDump ") + self.__getMbeanData("JBoss Logging Config Info", + "jboss-logging-config-info", + " get 'jboss.system:service=Logging,type=Log4jService' ") + + ## Get jboss.* Data + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="jboss-mbean-data" style="font-weight: bold;">– JBoss JMX MBean Data from <tt>jboss:*</tt></div> + """ + self.__getMbeanData("JBoss System Properties", + "jboss-system-properties-info", + " invoke 'jboss:name=SystemProperties,type=Service' showAll ") + + self.__getMbeanData("JBoss JNDI List View", + "jboss-jndi-list-info", + " invoke 'jboss:service=JNDIView' list true ") + + ## MBean Summary + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="jboss-mbean-summary" style="font-weight: bold;">– JBoss MBean Summary</div> + """ + self.__getMbeanData("JBoss MBean Vendor/Version Info", + "jboss-vendor-version", + " get 'JMImplementation:type=MBeanServerDelegate' ") + self.__getMbeanData("JBoss MBean Count", + "jboss-mbean-count", + " serverinfo -c ") + self.__getMbeanData("JBoss MBean List", + "jboss-mbean-list", + " serverinfo -l ") + + ##JBoss Messaging Data + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="jboss-messaging" style="font-weight: bold;">– JBoss JMX Messaging MBean Data from <tt>jboss.messaging:*</tt></div> + """ + self.__getMbeanData("JBoss Message Counters", + "jboss-message-counters", + " invoke 'jboss.messaging:service=ServerPeer' listMessageCountersAsHTML ") + + self.__getMbeanData("JBoss Prepared Transactions Table", + "jboss-prepared-transactions", + " invoke 'jboss.messaging:service=ServerPeer' listAllPreparedTransactions ") + + self.__getMbeanData("JBoss Active Clients Table", + "jboss-active-clients", + " invoke 'jboss.messaging:service=ServerPeer' showActiveClientsAsHTML ") + + ## Get j2ee Data query 'jboss.j2ee:*' + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="jboss-j2ee" style="font-weight: bold;">– JBoss JMX J2EE MBean Data from <tt>jboss.j2ee:*</tt></div> + """ + self.__getMbeanData("JBoss J2EE MBeans", + "jboss-j2ee-mbeans", + " query 'jboss.j2ee:*' ") + + ## VFS + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="jboss-vfs" style="font-weight: bold;">– JBoss JMX VFS MBean Data from <tt>jboss.vfs:*</tt></div> + """ + self.__getMbeanData("JBoss VFS Cached Contexts", + "jboss-vfs-contexts", + " invoke 'jboss.vfs:service=VFSCacheStatistics' listCachedContexts ") + + ## Get jsr77 Data + self.__jbossHTMLBody += """ + <br/> + <br/> + <div id="jboss-jsr77-data" style="font-weight: bold;">– JBoss JSR77 Data</div> + """ + self.__getMbeanData("JBoss JSR77 Data", + "jboss-jsr77", + " jsr77 ") + return + + + def __getFiles(self, configDirAry): + """ + This function will collect files from JBOSS_HOME for analysis. The scope of files to + be collected are determined by options to this SOS plug-in. + """ + + for dir in configDirAry: + path=os.path.join(self.__jbossHome, "server", dir) + ## First add forbidden files + self.addForbiddenPath(os.path.join(path, "tmp")) + self.addForbiddenPath(os.path.join(path, "work")) + self.addForbiddenPath(os.path.join(path, "data")) + + if os.path.exists(path): + ## First get everything in the conf dir + confDir=os.path.join(path, "conf") + self.doCopyFileOrDir(confDir) + ## Log dir next + logDir=os.path.join(path, "log") + + for logFile in find("*", logDir): + self.addCopySpecLimit(logFile, self.getOption("logsize")) + ## Deploy dir + deployDir=os.path.join(path, "deploy") + + for deployFile in find("*", deployDir, max_depth=1): + self.addCopySpec(deployFile) + + ## Get application deployment descriptors if designated. + if self.isOptionEnabled("appxml"): + appxml=self.getOption("appxml") + ## I'd rather use comma as the delimiter but getOption doesn't seem to be passing it through. + ## Since we are using spaces as the delimiter, we need to filter out empty list elements + ## if the user did something like ' all default web '. + appxml=appxml.split(' ') + ## Flter(None doesn't work. Allows 0. + appxml=filter(lambda x: len(x), appxml) + for app in appxml: + pat = os.path.join("*%s*" % (app,), "WEB-INF") + for file in find("*.xml", deployDir, path_pattern=pat): + self.addCopySpec(file) + return + + def setup(self): + + ## We need to know where JBoss is installed and if we can't find it we + ## must exit immediately. + if not self.__getJbossHome(): + self.exit_please() + return + + ## Check to see if the user passed in a limited list of server config jars. + self.__updateServerConfigDirs() + + ## Generate HTML Body for report + self.__createHTMLBodyStart() + + ## Generate hashes of the stock Jar files for the report. + if self.getOption("stdjar"): + self.__getStdJarInfo() + + ## Generate hashes for the Jars in the various profiles + if self.getOption("servjar"): + self.__getServerConfigJarInfo(self.__jbossServerConfigDirs) + + ## Generate a Tree for JBOSS_HOME + self.__getJBossHomeTree() + + if self.getOption("twiddle"): + ## We need to know where Java is installed or at least ensure that it + ## is available to the plug-in so that we can run twiddle. + self.__haveJava = self.__getJavaHome() + self.__buildTwiddleCmd() + self.__getTwiddleData() + + + self.addCustomText(self.__jbossHTMLBody) + + self.__getFiles(self.__jbossServerConfigDirs) + + return + + def postproc(self): + """ + Obfuscate passwords. + """ + + for dir in self.__jbossServerConfigDirs: + path=os.path.join(self.__jbossHome, "server", dir) + ## Really annoying that there appears to be no vehicle to + ## say I want ignore case...argh! + self.doRegexSub(os.path.join(path,"conf","login-config.xml"), + r"\"[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd]\".*>.*</[Mm][Oo][Dd][Uu][Ll][Ee]-[Oo][Pp][Tt][Ii][Oo][Nn].*>", + r'"password">********</module-option>') + + tmp = os.path.join(path,"conf", "props") + for propFile in find("*-users.properties", tmp): + self.doRegexSub(propFile, + r"=(.*)", + r'=********') + + ## Remove PW from -ds.xml files + tmp=os.path.join(path, "deploy") + for dsFile in find("*-ds.xml", tmp): + self.doRegexSub(dsFile, + r"<[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd].*>.*</[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd].*>", + r"<password>********</password>") + return diff --git a/sos/plugins/kdump.py b/sos/plugins/kdump.py index bea5d0b9..3a138693 100644 --- a/sos/plugins/kdump.py +++ b/sos/plugins/kdump.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class kdump(sos.plugintools.PluginBase): +class kdump(Plugin, RedHatPlugin): """Kdump related information """ def checkenabled(self): diff --git a/sos/plugins/kernel.py b/sos/plugins/kernel.py index c43dbdf4..8a0e860c 100644 --- a/sos/plugins/kernel.py +++ b/sos/plugins/kernel.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os, re -class kernel(sos.plugintools.PluginBase): +class kernel(Plugin, RedHatPlugin): """kernel related information """ optionList = [("modinfo", 'gathers information on all kernel modules', 'fast', True)] @@ -47,7 +47,7 @@ class kernel(sos.plugintools.PluginBase): if self.getOption('modinfo'): runcmd = "" ret, mods, rtime = self.callExtProg('/sbin/lsmod | /bin/cut -f1 -d" " 2>/dev/null | /bin/grep -v Module 2>/dev/null') - for kmod in mods.split('\n'): + for kmod in mods.split('\n'): if '' != kmod.strip(): runcmd = runcmd + " " + kmod if len(runcmd): diff --git a/sos/plugins/kvm.py b/sos/plugins/kvm.py index 111bcf97..46624282 100644 --- a/sos/plugins/kvm.py +++ b/sos/plugins/kvm.py @@ -15,10 +15,10 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class kvm(sos.plugintools.PluginBase): +class kvm(Plugin, RedHatPlugin): """KVM related information """ diff --git a/sos/plugins/ldap.py b/sos/plugins/ldap.py index 81ec53aa..b331c26e 100644 --- a/sos/plugins/ldap.py +++ b/sos/plugins/ldap.py @@ -12,16 +12,16 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class ldap(sos.plugintools.PluginBase): +class ldap(Plugin, RedHatPlugin): """LDAP related information """ def checkenabled(self): self.packages = [ "openldap" ] self.files = [ "/etc/openldap/ldap.conf" ] - return sos.plugintools.PluginBase.checkenabled(self) + return Plugin.checkenabled(self) def get_ldap_opts(self): # capture /etc/openldap/ldap.conf options in dict diff --git a/sos/plugins/libraries.py b/sos/plugins/libraries.py index 39c21467..bf0223a2 100644 --- a/sos/plugins/libraries.py +++ b/sos/plugins/libraries.py @@ -12,13 +12,13 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class libraries(sos.plugintools.PluginBase): +class libraries(Plugin, RedHatPlugin): """information on shared libraries """ - optionList = [('ldconfigv', 'the name of each directory as it is scanned, and any links that are created.', + optionList = [('ldconfigv', 'the name of each directory as it is scanned, and any links that are created.', "slow", False)] def setup(self): diff --git a/sos/plugins/libvirt.py b/sos/plugins/libvirt.py index 63d62bea..9f2a18d5 100644 --- a/sos/plugins/libvirt.py +++ b/sos/plugins/libvirt.py @@ -12,8 +12,8 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools -class libvirt(sos.plugintools.PluginBase): +from sos.plugins import Plugin, RedHatPlugin +class libvirt(Plugin, RedHatPlugin): """libvirt-related information """ def setup(self): diff --git a/sos/plugins/logrotate.py b/sos/plugins/logrotate.py index adc65aae..660623ae 100644 --- a/sos/plugins/logrotate.py +++ b/sos/plugins/logrotate.py @@ -12,12 +12,12 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class logrotate(sos.plugintools.PluginBase): +class logrotate(Plugin, RedHatPlugin): """logrotate configuration files and debug info """ - + def setup(self): self.collectExtOutput("/usr/sbin/logrotate --debug /etc/logrotate.conf", suggest_filename = "logrotate_debug") diff --git a/sos/plugins/lsbrelease.py b/sos/plugins/lsbrelease.py index 7a58a024..782038e1 100644 --- a/sos/plugins/lsbrelease.py +++ b/sos/plugins/lsbrelease.py @@ -13,10 +13,10 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class lsbrelease(sos.plugintools.PluginBase): +class lsbrelease(Plugin, RedHatPlugin): """Linux Standard Base information """ def diagnose(self): diff --git a/sos/plugins/memory.py b/sos/plugins/memory.py index 8d4cf202..3ee8ef7e 100644 --- a/sos/plugins/memory.py +++ b/sos/plugins/memory.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class memory(sos.plugintools.PluginBase): +class memory(Plugin, RedHatPlugin): """memory usage information """ def setup(self): @@ -23,7 +23,7 @@ class memory(sos.plugintools.PluginBase): "/proc/meminfo", "/proc/vmstat", "/proc/slabinfo"]) - + self.collectExtOutput("/bin/dmesg | grep -e 'e820.' -e 'aperature.'") self.collectExtOutput("/usr/bin/free", root_symlink = "free") self.collectExtOutput("/usr/bin/free -m") diff --git a/sos/plugins/mrggrid.py b/sos/plugins/mrggrid.py index 54e1d6a4..b47fd635 100644 --- a/sos/plugins/mrggrid.py +++ b/sos/plugins/mrggrid.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class mrggrid(sos.plugintools.PluginBase): +class mrggrid(Plugin, RedHatPlugin): """MRG GRID related information """ def setup(self): diff --git a/sos/plugins/mrgmessg.py b/sos/plugins/mrgmessg.py index 2c01fa87..7668b6b9 100644 --- a/sos/plugins/mrgmessg.py +++ b/sos/plugins/mrgmessg.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class mrgmessg(sos.plugintools.PluginBase): +class mrgmessg(Plugin, RedHatPlugin): """MRG Messaging related information """ def setup(self): diff --git a/sos/plugins/mysql.py b/sos/plugins/mysql.py index 59b6bb28..5c7049db 100644 --- a/sos/plugins/mysql.py +++ b/sos/plugins/mysql.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class mysql(sos.plugintools.PluginBase): +class mysql(Plugin, RedHatPlugin): """MySQL related information """ @@ -23,7 +23,7 @@ class mysql(sos.plugintools.PluginBase): return self.isInstalled("mysql-server") or \ exists("/etc/my.cnf") or \ self.isInstalled("mysql") - + def setup(self): self.addCopySpecs([ "/etc/my.cnf", diff --git a/sos/plugins/named.py b/sos/plugins/named.py index 6710a165..8e6af6dd 100644 --- a/sos/plugins/named.py +++ b/sos/plugins/named.py @@ -12,19 +12,19 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import commands from os.path import normpath, join, exists -class named(sos.plugintools.PluginBase): +class named(Plugin, RedHatPlugin): """named related information """ def checkenabled(self): self.files = [ "/etc/named.conf", "/etc/sysconfig/named" ] self.packages = [ "bind" ] - return sos.plugintools.PluginBase.checkenabled(self) - + return Plugin.checkenabled(self) + def getDnsDir(self, configFile): """ grab directory path from named{conf,boot} """ diff --git a/sos/plugins/netdump.py b/sos/plugins/netdump.py index 1dcbc0f0..815c5955 100644 --- a/sos/plugins/netdump.py +++ b/sos/plugins/netdump.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class netdump(sos.plugintools.PluginBase): +class netdump(Plugin, RedHatPlugin): """Netdump Configuration Information """ def checkenabled(self): diff --git a/sos/plugins/networking.py b/sos/plugins/networking.py index fbc8d67d..b0b3010c 100644 --- a/sos/plugins/networking.py +++ b/sos/plugins/networking.py @@ -12,11 +12,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os import re -class networking(sos.plugintools.PluginBase): +class networking(Plugin, RedHatPlugin): """network related information """ optionList = [("traceroute", "collects a traceroute to rhn.redhat.com", "slow", False)] diff --git a/sos/plugins/nfsserver.py b/sos/plugins/nfsserver.py index 5dad1676..876192ab 100644 --- a/sos/plugins/nfsserver.py +++ b/sos/plugins/nfsserver.py @@ -14,11 +14,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os from stat import ST_SIZE -class nfsserver(sos.plugintools.PluginBase): +class nfsserver(Plugin, RedHatPlugin): """NFS server-related information """ def checkenabled(self): diff --git a/sos/plugins/nscd.py b/sos/plugins/nscd.py index 8ae28878..6fcc3ad2 100644 --- a/sos/plugins/nscd.py +++ b/sos/plugins/nscd.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class nscd(sos.plugintools.PluginBase): +class nscd(Plugin, RedHatPlugin): """NSCD related information """ diff --git a/sos/plugins/ntp.py b/sos/plugins/ntp.py index f3010ae4..bd9b1a76 100644 --- a/sos/plugins/ntp.py +++ b/sos/plugins/ntp.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class ntp(sos.plugintools.PluginBase): +class ntp(Plugin, RedHatPlugin): """NTP related information """ def setup(self): diff --git a/sos/plugins/oddjob.py b/sos/plugins/oddjob.py index 64d40784..19f666a7 100644 --- a/sos/plugins/oddjob.py +++ b/sos/plugins/oddjob.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class oddjob(sos.plugintools.PluginBase): +class oddjob(Plugin, RedHatPlugin): """oddjob related information """ def checkenabled(self): diff --git a/sos/plugins/openssl.py b/sos/plugins/openssl.py index 629f3d7b..35911593 100644 --- a/sos/plugins/openssl.py +++ b/sos/plugins/openssl.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class openssl(sos.plugintools.PluginBase): +class openssl(Plugin, RedHatPlugin): """openssl related information """ diff --git a/sos/plugins/openswan.py b/sos/plugins/openswan.py index 442531f2..bd9de349 100644 --- a/sos/plugins/openswan.py +++ b/sos/plugins/openswan.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class openswan(sos.plugintools.PluginBase): +class openswan(Plugin, RedHatPlugin): """ipsec related information """ def checkenabled(self): diff --git a/sos/plugins/pam.py b/sos/plugins/pam.py index 5143f7b2..08b076c7 100644 --- a/sos/plugins/pam.py +++ b/sos/plugins/pam.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class pam(sos.plugintools.PluginBase): +class pam(Plugin, RedHatPlugin): """PAM related information """ def setup(self): diff --git a/sos/plugins/postfix.py b/sos/plugins/postfix.py index 32932468..1c604898 100644 --- a/sos/plugins/postfix.py +++ b/sos/plugins/postfix.py @@ -12,15 +12,15 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class postfix(sos.plugintools.PluginBase): +class postfix(Plugin, RedHatPlugin): """mail server related information """ def checkenabled(self): return self.isInstalled("postfix") or exists("/etc/rc.d/init.d/postfix") - + def setup(self): self.addCopySpecs([ "/etc/mail", diff --git a/sos/plugins/postgresql.py b/sos/plugins/postgresql.py new file mode 100644 index 00000000..f3a44eb3 --- /dev/null +++ b/sos/plugins/postgresql.py @@ -0,0 +1,56 @@ +import os +import fnmatch +import shlex +import subprocess +import tempfile + +from sos.plugins import Plugin, RedHatPlugin +from sos.utilities import find + +class postgresql(Plugin, RedHatPlugin): + """PostgreSQL related information""" + + optionList = [ + ("pghome", 'PostgreSQL server home directory.', '', '/var/lib/pgsql'), + ("username", 'username for pg_dump', '', 'postgres'), + ("password", 'password for pg_dump', '', ''), + ("dbname", 'database name to dump for pg_dump', '', ''), + ] + + def pg_dump(self): + dest_file = os.path.join(self.tmp_dir, "sos_pgdump.tar") + old_env_pgpassword = os.environ.get("PGPASSWORD") + os.environ["PGPASSWORD"] = self.getOption("password") + (status, output, rtime) = self.callExtProg("pg_dump %s -U %s -w -f %s -F t" % + (self.getOption("dbname"), + self.getOption("username"), + dest_file)) + if old_env_pgpassword is not None: + os.environ["PGPASSWORD"] = old_env_pgpassword + if (status == 0): + self.addCopySpec(dest_file) + else: + self.addAlert("ERROR: Unable to execute pg_dump. Error(%s)" % (output)) + + def setup(self): + if self.getOption("dbname"): + if self.getOption("password"): + self.tmp_dir = tempfile.mkdtemp() + self.pg_dump() + else: + self.addAlert("WARN: password must be supplied to dump a database.") + + # Copy PostgreSQL log files. + for file in find("*.log", self.getOption("pghome")): + self.addCopySpec(file) + # Copy PostgreSQL config files. + for file in find("*.conf", self.getOption("pghome")): + self.addCopySpec(file) + + self.addCopySpec(os.path.join(self.getOption("pghome"), "data" , "PG_VERSION")) + self.addCopySpec(os.path.join(self.getOption("pghome"), "data" , "postmaster.opts")) + + + def postproc(self): + import shutil + shutil.rmtree(self.tmp_dir) diff --git a/sos/plugins/ppp.py b/sos/plugins/ppp.py index 08f45585..98c68a84 100644 --- a/sos/plugins/ppp.py +++ b/sos/plugins/ppp.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class ppp(sos.plugintools.PluginBase): +class ppp(Plugin, RedHatPlugin): """ppp, wvdial and rp-pppoe related information """ def checkenabled(self): diff --git a/sos/plugins/printing.py b/sos/plugins/printing.py index 15353fe1..48c25901 100644 --- a/sos/plugins/printing.py +++ b/sos/plugins/printing.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class printing(sos.plugintools.PluginBase): +class printing(Plugin, RedHatPlugin): """printing related information (cups) """ optionList = [("cups", "max size (MiB) to collect per cups log file", diff --git a/sos/plugins/process.py b/sos/plugins/process.py index ea19be55..48ec4bd6 100644 --- a/sos/plugins/process.py +++ b/sos/plugins/process.py @@ -12,11 +12,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import time import os -class process(sos.plugintools.PluginBase): +class process(Plugin, RedHatPlugin): """process information """ def setup(self): diff --git a/sos/plugins/psacct.py b/sos/plugins/psacct.py index 3862f902..3c8765a0 100644 --- a/sos/plugins/psacct.py +++ b/sos/plugins/psacct.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class psacct(sos.plugintools.PluginBase): +class psacct(Plugin, RedHatPlugin): """Process accounting related information """ def setup(self): diff --git a/sos/plugins/pxe.py b/sos/plugins/pxe.py index 018718fb..3a00d478 100644 --- a/sos/plugins/pxe.py +++ b/sos/plugins/pxe.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class pxe(sos.plugintools.PluginBase): +class pxe(Plugin, RedHatPlugin): """PXE related information """ @@ -23,7 +23,7 @@ class pxe(sos.plugintools.PluginBase): def checkenabled(self): return self.isInstalled("system-config-netboot-cmd") or exists("/usr/sbin/pxeos") - + def setup(self): self.collectExtOutput("/usr/sbin/pxeos -l") self.addCopySpec("/etc/dhcpd.conf") diff --git a/sos/plugins/qpidd.py b/sos/plugins/qpidd.py index 5684806b..564da7d2 100644 --- a/sos/plugins/qpidd.py +++ b/sos/plugins/qpidd.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class qpidd(sos.plugintools.PluginBase): +class qpidd(Plugin, RedHatPlugin): """Messaging related information """ def checkenabled(self): diff --git a/sos/plugins/quagga.py b/sos/plugins/quagga.py index 22459676..013d41ab 100644 --- a/sos/plugins/quagga.py +++ b/sos/plugins/quagga.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class quagga(sos.plugintools.PluginBase): +class quagga(Plugin, RedHatPlugin): """quagga related information """ diff --git a/sos/plugins/radius.py b/sos/plugins/radius.py index 8b75bf65..f9621aeb 100644 --- a/sos/plugins/radius.py +++ b/sos/plugins/radius.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class radius(sos.plugintools.PluginBase): +class radius(Plugin, RedHatPlugin): """radius related information """ def checkenabled(self): diff --git a/sos/plugins/rhevm.py b/sos/plugins/rhevm.py new file mode 100644 index 00000000..236ead49 --- /dev/null +++ b/sos/plugins/rhevm.py @@ -0,0 +1,23 @@ +from sos.plugins import Plugin, RedHatPlugin + +# Class name must be the same as file name and method names must not change +class rhevm(Plugin, RedHatPlugin): + """Nogah related information""" + + optionList = [("vdsmlogs", 'Directory containing all of the SOS logs from the RHEV hypervisor(s)', '', False)] + + def setup(self): + # Copy rhevm config files. + self.addCopySpec("/etc/rhevm") + self.addCopySpec("/var/log/rhevm") + if self.getOption("vdsmlogs"): + self.addCopySpec(self.getOption("vdsmlogs")) + + def postproc(self): + """ + Obfuscate passwords. + """ + + self.doRegexSub("/etc/rhevm/rhevm-config/rhevm-config.properties", + r"Password.type=(.*)", + r'Password.type=********') diff --git a/sos/plugins/rhn.py b/sos/plugins/rhn.py index 73bb7e8a..c94d17b1 100644 --- a/sos/plugins/rhn.py +++ b/sos/plugins/rhn.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class rhn(sos.plugintools.PluginBase): +class rhn(Plugin, RedHatPlugin): """RHN Satellite related information """ satellite = False diff --git a/sos/plugins/rpm.py b/sos/plugins/rpm.py index 33529cee..511f64ca 100644 --- a/sos/plugins/rpm.py +++ b/sos/plugins/rpm.py @@ -12,14 +12,14 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class rpm(sos.plugintools.PluginBase): +class rpm(Plugin, RedHatPlugin): """RPM information """ optionList = [("rpmq", "queries for package information via rpm -q", "fast", True), ("rpmva", "runs a verify on all packages", "slow", False)] - + def setup(self): self.addCopySpec("/var/log/rpmpkgs") diff --git a/sos/plugins/s390.py b/sos/plugins/s390.py index a5445ee7..b0b3472a 100644 --- a/sos/plugins/s390.py +++ b/sos/plugins/s390.py @@ -15,9 +15,9 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import os -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class s390(sos.plugintools.PluginBase): +class s390(Plugin, RedHatPlugin): """s390 related information """ diff --git a/sos/plugins/samba.py b/sos/plugins/samba.py index c88bb371..70273057 100644 --- a/sos/plugins/samba.py +++ b/sos/plugins/samba.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class samba(sos.plugintools.PluginBase): +class samba(Plugin, RedHatPlugin): """Samba related information """ def setup(self): diff --git a/sos/plugins/sar.py b/sos/plugins/sar.py index b017ffa9..50e6077b 100644 --- a/sos/plugins/sar.py +++ b/sos/plugins/sar.py @@ -12,11 +12,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os import listdir from os.path import exists -class sar(sos.plugintools.PluginBase): +class sar(Plugin, RedHatPlugin): """Generate the sar file from /var/log/sa/saXX files """ def setup(self): @@ -31,4 +31,4 @@ class sar(sos.plugintools.PluginBase): self.collectOutputNow(sar_command, sar_filename, root_symlink=sar_filename) def checkenabled(self): - return exists("/var/log/sa") and os.path.exists("/usr/bin/sar") + return exists("/var/log/sa") and exists("/usr/bin/sar") diff --git a/sos/plugins/selinux.py b/sos/plugins/selinux.py index 92bfc630..0f80ed9a 100644 --- a/sos/plugins/selinux.py +++ b/sos/plugins/selinux.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class selinux(sos.plugintools.PluginBase): +class selinux(Plugin, RedHatPlugin): """selinux related information """ optionList = [("fixfiles", 'Print incorrect file context labels', 'slow', False)] diff --git a/sos/plugins/sendmail.py b/sos/plugins/sendmail.py index 0b73451b..d7672da4 100644 --- a/sos/plugins/sendmail.py +++ b/sos/plugins/sendmail.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class sendmail(sos.plugintools.PluginBase): +class sendmail(Plugin, RedHatPlugin): """sendmail information """ def checkenabled(self): diff --git a/sos/plugins/smartcard.py b/sos/plugins/smartcard.py index ce628501..2f9ee3c2 100644 --- a/sos/plugins/smartcard.py +++ b/sos/plugins/smartcard.py @@ -14,11 +14,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os from time import time -class smartcard(sos.plugintools.PluginBase): +class smartcard(Plugin, RedHatPlugin): """Smart Card related information """ diff --git a/sos/plugins/snmp.py b/sos/plugins/snmp.py index 896c8d7b..81536f57 100644 --- a/sos/plugins/snmp.py +++ b/sos/plugins/snmp.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class snmp(sos.plugintools.PluginBase): +class snmp(Plugin, RedHatPlugin): """snmp related information """ def checkenabled(self): diff --git a/sos/plugins/soundcard.py b/sos/plugins/soundcard.py index 33a31c1f..c70729ba 100644 --- a/sos/plugins/soundcard.py +++ b/sos/plugins/soundcard.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class soundcard(sos.plugintools.PluginBase): +class soundcard(Plugin, RedHatPlugin): """ Sound card information """ diff --git a/sos/plugins/squid.py b/sos/plugins/squid.py index a6a8ed44..b7f58371 100644 --- a/sos/plugins/squid.py +++ b/sos/plugins/squid.py @@ -12,16 +12,16 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class squid(sos.plugintools.PluginBase): +class squid(Plugin, RedHatPlugin): """squid related information """ def checkenabled(self): self.files = [ "/etc/squid/squid.conf" ] self.packages = [ "squid" ] - return sos.plugintools.PluginBase.checkenabled(self) + return Plugin.checkenabled(self) def setup(self): self.addCopySpec("/etc/squid/squid.conf") diff --git a/sos/plugins/ssh.py b/sos/plugins/ssh.py index ebfb2772..9cc02345 100644 --- a/sos/plugins/ssh.py +++ b/sos/plugins/ssh.py @@ -14,9 +14,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class ssh(sos.plugintools.PluginBase): +class ssh(Plugin, RedHatPlugin): """ssh-related information """ def setup(self): diff --git a/sos/plugins/sssd.py b/sos/plugins/sssd.py index 45e962bc..641ae916 100644 --- a/sos/plugins/sssd.py +++ b/sos/plugins/sssd.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class sssd(sos.plugintools.PluginBase): +class sssd(Plugin, RedHatPlugin): """sssd-related Diagnostic Information """ diff --git a/sos/plugins/startup.py b/sos/plugins/startup.py index a8b70088..eafca6d9 100644 --- a/sos/plugins/startup.py +++ b/sos/plugins/startup.py @@ -12,16 +12,16 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class startup(sos.plugintools.PluginBase): +class startup(Plugin, RedHatPlugin): """startup information """ optionList = [("servicestatus", "get a status of all running services", "slow", False)] def setup(self): self.addCopySpec("/etc/rc.d") - + self.collectExtOutput("LC_ALL=C /sbin/chkconfig --list", root_symlink = "chkconfig") if self.getOption('servicestatus'): self.collectExtOutput("/sbin/service --status-all") diff --git a/sos/plugins/system.py b/sos/plugins/system.py index 68033475..73becd5e 100644 --- a/sos/plugins/system.py +++ b/sos/plugins/system.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class system(sos.plugintools.PluginBase): +class system(Plugin, RedHatPlugin): """core system related information """ def setup(self): diff --git a/sos/plugins/systemtap.py b/sos/plugins/systemtap.py index ff44d983..0f227a36 100644 --- a/sos/plugins/systemtap.py +++ b/sos/plugins/systemtap.py @@ -14,15 +14,15 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class systemtap(sos.plugintools.PluginBase): +class systemtap(Plugin, RedHatPlugin): """SystemTap information """ def checkenabled(self): self.files = [ "/usr/bin/stap" ] self.packages = [ "systemtap", "systemtap-runtime" ] - return sos.plugintools.PluginBase.checkenabled(self) + return Plugin.checkenabled(self) def setup(self): self.collectExtOutput("/usr/bin/stap -V 2") diff --git a/sos/plugins/tftpserver.py b/sos/plugins/tftpserver.py index 500dc535..5b563d84 100644 --- a/sos/plugins/tftpserver.py +++ b/sos/plugins/tftpserver.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class tftpserver(sos.plugintools.PluginBase): +class tftpserver(Plugin, RedHatPlugin): """tftpserver related information """ def checkenabled(self): diff --git a/sos/plugins/tomcat.py b/sos/plugins/tomcat.py index 64e0146c..382e8531 100644 --- a/sos/plugins/tomcat.py +++ b/sos/plugins/tomcat.py @@ -12,13 +12,13 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class tomcat(sos.plugintools.PluginBase): +class tomcat(Plugin, RedHatPlugin): """Tomcat related information """ def checkenabled(self): return self.isInstalled("tomcat5") - + def setup(self): self.addCopySpecs(["/etc/tomcat5", "/var/log/tomcat5"]) diff --git a/sos/plugins/udev.py b/sos/plugins/udev.py index 7a0c4896..4a71d02d 100644 --- a/sos/plugins/udev.py +++ b/sos/plugins/udev.py @@ -12,9 +12,9 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin -class udev(sos.plugintools.PluginBase): +class udev(Plugin, RedHatPlugin): """udev related information """ def setup(self): diff --git a/sos/plugins/veritas.py b/sos/plugins/veritas.py index 7359bbfb..95564579 100644 --- a/sos/plugins/veritas.py +++ b/sos/plugins/veritas.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class veritas(sos.plugintools.PluginBase): +class veritas(Plugin, RedHatPlugin): """Veritas related information """ # Information about VRTSexplorer obtained from @@ -24,7 +24,7 @@ class veritas(sos.plugintools.PluginBase): def checkenabled(self): return os.path.isfile(self.getOption("script")) - + def setup(self): """ interface with vrtsexplorer to capture veritas related data """ stat, out, runtime = self.callExtProg(self.getOption("script")) diff --git a/sos/plugins/vmware.py b/sos/plugins/vmware.py index 074b6524..a833df2e 100644 --- a/sos/plugins/vmware.py +++ b/sos/plugins/vmware.py @@ -12,15 +12,15 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class vmware(sos.plugintools.PluginBase): +class vmware(Plugin, RedHatPlugin): """VMWare related information """ def checkenabled(self): return exists("/usr/bin/vmware") - + def setup(self): self.collectExtOutput("/usr/bin/vmware -v") self.addCopySpecs(["/etc/vmware/locations", "/etc/vmware/config"]) diff --git a/sos/plugins/x11.py b/sos/plugins/x11.py index 7b8d08e7..16b29d74 100644 --- a/sos/plugins/x11.py +++ b/sos/plugins/x11.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin from os.path import exists -class x11(sos.plugintools.PluginBase): +class x11(Plugin, RedHatPlugin): """X related information """ def checkenabled(self): @@ -26,6 +26,6 @@ class x11(sos.plugintools.PluginBase): "/etc/X11", "/var/log/Xorg.*.log", "/var/log/XFree86.*.log", - ) + ]) self.addForbiddenPath("/etc/X11/X") self.addForbiddenPath("/etc/X11/fontpath.d") diff --git a/sos/plugins/xen.py b/sos/plugins/xen.py index 9eeae3a7..be0f6b9c 100644 --- a/sos/plugins/xen.py +++ b/sos/plugins/xen.py @@ -12,12 +12,12 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os import re from stat import * -class xen(sos.plugintools.PluginBase): +class xen(Plugin, RedHatPlugin): """Xen related information """ def determineXenHost(self): @@ -79,7 +79,7 @@ class xen(sos.plugintools.PluginBase): self.collectExtOutput("/usr/sbin/xm list --long") self.collectExtOutput("/usr/sbin/brctl show") self.domCollectProc() - if self.is_running_xenstored(): + if self.is_running_xenstored(): self.addCopySpec("/sys/hypervisor/uuid") self.collectExtOutput("/usr/bin/xenstore-ls") else: diff --git a/sos/plugins/xinetd.py b/sos/plugins/xinetd.py index 323e3814..6458cd5c 100644 --- a/sos/plugins/xinetd.py +++ b/sos/plugins/xinetd.py @@ -14,10 +14,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class xinetd(sos.plugintools.PluginBase): +class xinetd(Plugin, RedHatPlugin): """xinetd information """ def checkenabled(self): diff --git a/sos/plugins/yum.py b/sos/plugins/yum.py index 41dc268d..0c2ad2f0 100644 --- a/sos/plugins/yum.py +++ b/sos/plugins/yum.py @@ -12,10 +12,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import sos.plugintools +from sos.plugins import Plugin, RedHatPlugin import os -class yum(sos.plugintools.PluginBase): +class yum(Plugin, RedHatPlugin): """yum information """ @@ -25,7 +25,7 @@ class yum(sos.plugintools.PluginBase): def checkenabled(self): self.files = [ "/etc/yum.conf" ] self.packages = [ "yum" ] - return sos.plugintools.PluginBase.checkenabled(self) + return Plugin.checkenabled(self) def analyze(self): # repo sanity checking diff --git a/sos/plugintools.py b/sos/plugintools.py deleted file mode 100644 index 8aa99600..00000000 --- a/sos/plugintools.py +++ /dev/null @@ -1,560 +0,0 @@ -## 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 sos import _sos as _ -import os, os.path, sys, string, glob, re, traceback -import shutil -from stat import * -from time import time -from itertools import * -from collections import deque - -class PluginException(Exception): pass - -class PluginBase: - """ - Base class for plugins - """ - def __init__(self, pluginname, commons): - if not getattr(self, "optionList", False): - self.optionList = deque() - - self.copiedFiles = deque() - self.copiedDirs = deque() - self.executedCommands = deque() - self.diagnose_msgs = deque() - self.alerts = deque() - self.customText = "" - self.optNames = deque() - self.optParms = deque() - self.piName = pluginname - self.cInfo = commons - self.forbiddenPaths = deque() - self.copyPaths = deque() - self.collectProgs = deque() - - self.packages = deque() - self.files = deque() - - self.must_exit = False - - self.soslog = logging.getLogger('sos') - self.proflog = logging.getLogger('sosprofile') - - # 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]}) - - def policy(self): - return self.cInfo["policy"] - - def isInstalled(self, package_name): - '''Is the package $package_name installed? - ''' - return (self.policy().pkgByName(package_name) != {}) - - # 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 afile['srcpath'] == srcpath: - abspath = os.path.join(self.cInfo['dstroot'], 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.debug("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) - ''' - try: - return re.findall(regex, open(fname, 'r').read(), re.MULTILINE) - except: # IOError, AttributeError, etc. - return [] - - # 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 - ''' - if self.cInfo['cmdlineopts'].profiler: - start_time = time() - - 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.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.debug("creating symlink %s -> %s" % (dstslname, rpth)) - - try: - os.symlink(rpth, dstslname) - except OSError: - # self.soslog.debug("skipping symlink creation: already exists (%s)" % dstslname) - return - - 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.debug("copying file %s" % srcpath) - try: - tdstpath, abspath = self.__copyFile(srcpath) - except EnvironmentError: - # self.soslog.debug("error copying file %s (already exists)" % (srcpath)) - return - except IOError: - # self.soslog.debug("error copying file %s (IOError)" % (srcpath)) - return - except: - # self.soslog.debug("error copying file %s (SOMETHING HAPPENED)" % (srcpath)) - return - - self.copiedFiles.append({'srcpath':srcpath, 'dstpath':tdstpath, 'symlink':"no"}) # save in our list - - if self.cInfo['cmdlineopts'].profiler: - time_passed = time() - start_time - self.proflog.debug("copied: %-75s time: %f" % (srcpath, time_passed)) - - return abspath - - def __copyFile(self, src): - """ call cp to copy a file, collect return status and output. Returns the - destination file name. - """ - rel_dir = os.path.dirname(src).lstrip(os.path.sep) - new_dir = os.path.join(self.cInfo['dstroot'], rel_dir) - new_fname = os.path.join(new_dir, os.path.basename(src)) - - if not os.path.exists(new_fname): - if not os.path.isdir(new_dir): - os.makedirs(new_dir) - - if os.path.islink(src): - linkto = os.readlink(src) - os.symlink(linkto, new_fname) - else: - fsrc = open(src,'r') - fdst = open(new_fname, 'w') - shutil.copyfileobj(fsrc, fdst, -1) - fsrc.close() - fdst.close() - else: - raise PluginException('Error copying file: already exists') - - abspath = os.path.join(self.cInfo['dstroot'], src.lstrip(os.path.sep)) - relpath = sosRelPath(self.cInfo['rptdir'], abspath) - return (relpath, abspath) - - 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, value): - ''' set the named option to value. - ''' - for name, parms in izip(self.optNames, self.optParms): - if name == optionname: - parms['enabled'] = value - return True - else: - return False - - def isOptionEnabled(self, optionname): - ''' Deprecated, use getOption() instead - ''' - return self.getOption(optionname) - - def getOption(self, optionname): - ''' see whether the named option is enabled. - ''' - for name, parms in izip(self.optNames, self.optParms): - if name == optionname: - return parms['enabled'] - # nonexistent options aren't enabled. - return 0 - - def addCopySpecLimit(self, fname, sizelimit = None): - """Add a file specification (with limits) - """ - if not ( fname and len(fname) ): - # self.soslog.warning("invalid file path") - return False - files = glob.glob(fname) - 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:] + ".tailed") - - def addCopySpecs(self, copyspecs): - for copyspec in copyspecs: - self.addCopySpec(copyspec) - - def addCopySpec(self, copyspec): - """ Add a file specification (can be file, dir,or shell glob) to be - copied into the sosreport by this module - """ - if not (copyspec and len(copyspec)): - # self.soslog.warning("invalid file path") - return False - # Glob case handling is such that a valid non-glob is a reduced glob - for filespec in glob.glob(copyspec): - if filespec not in self.copyPaths: - self.copyPaths.append(filespec) - - def callExtProg(self, prog): - """ Execute a command independantly of the output gathering part of - sosreport - """ - # pylint: disable-msg = W0612 - status, shout, runtime = sosGetCommandOutput(prog) - return (status, shout, runtime) - - def collectExtOutput(self, exe, suggest_filename = None, root_symlink = None, timeout = 300): - """ - Run a program and collect the output - """ - self.collectProgs.append( (exe, suggest_filename, root_symlink, timeout) ) - - def fileGrep(self, regexp, fname): - try: - return [l for l in open(fname).readlines() if re.match(regexp, l)] - except: # IOError, AttributeError, etc. - return [] - - 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, root_symlink = False, timeout = 300): - """ Execute a command and save the output to a file for inclusion in - the report - """ - - if self.cInfo['cmdlineopts'].profiler: - start_time = time() - - # pylint: disable-msg = W0612 - status, shout, runtime = sosGetCommandOutput(exe, timeout = timeout) - - 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): # if not command_not_found - outfd = open(outfn, "w") - if len(shout): - outfd.write(shout+"\n") - outfd.close() - - if root_symlink: - curdir = os.getcwd() - os.chdir(self.cInfo['dstroot']) - try: - dst_from_root = outfn[len(self.cInfo['dstroot'])+1:] - target = ("../" * string.count(dst_from_root, "/")) + dst_from_root - os.symlink(target, root_symlink.strip("/.")) - except: - pass - os.chdir(curdir) - - outfn_strip = outfn[len(self.cInfo['cmddir'])+1:] - - else: - # self.soslog.debug("could not run command: %s" % exe) - outfn = None - outfn_strip = None - - # 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) - - if self.cInfo['cmdlineopts'].profiler: - time_passed = time() - start_time - self.proflog.debug("output: %-75s time: %f" % (exe, time_passed)) - - 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) - - # 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) - - 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 += text - - def copyStuff(self): - """ - Collect the data for a plugin - """ - copyMap = map(self.doCopyFileOrDir, self.copyPaths) - - for progs in izip(self.collectProgs): - prog, suggest_filename, root_symlink, timeout = progs[0] - # self.soslog.debug("collecting output of '%s'" % prog) - try: - self.collectOutputNow(prog, suggest_filename, root_symlink, timeout) - except Exception, e: - self.soslog.debug("error collection output of '%s', traceback follows:" % prog) - - def exit_please(self): - """ This function tells the plugin that it should exit ASAP""" - self.must_exit = True - - 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. - """ - # some files or packages have been specified for this package - if len(self.files) or len(self.packages): - for fname in self.files: - if os.path.exists(fname): - return True - for pkgname in self.packages: - if self.isInstalled(pkgname): - return True - return False - - 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 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 -# vim:ts=4 sw=4 et diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py new file mode 100644 index 00000000..07c75f64 --- /dev/null +++ b/sos/policies/__init__.py @@ -0,0 +1,276 @@ +import os +import platform +import time + +from sos.utilities import ImporterHelper, import_module, get_hash_name +from sos.plugins import IndependentPlugin +from sos import _sos as _ +import hashlib + +def import_policy(name): + policy_fqname = "sos.policies.%s" % name + try: + return import_module(policy_fqname, Policy) + except ImportError: + return None + +def load(cache={}): + if 'policy' in cache: + return cache.get('policy') + + import sos.policies + helper = ImporterHelper(sos.policies) + for module in helper.get_modules(): + for policy in import_policy(module): + if policy.check(): + cache['policy'] = policy() + return policy() + raise Exception("No policy could be loaded.") + + +class PackageManager(object): + + def allPkgsByName(self, name): + """ + Return a list of packages that match name. + """ + return [] + + def allPkgsByNameRegex(self, regex_name, flags=None): + """ + Return a list of packages that match regex_name. + """ + return [] + + def pkgByName(self, name): + """ + Return a single package that matches name. + """ + return None + + def allPkgs(self): + """ + Return a list of all packages. + """ + return [] + + +class Policy(object): + + msg = _("""This utility will collect some detailed information about the +hardware and setup of your %(distro)s system. +The information is collected and an archive is packaged under +/tmp, which you can send to a support representative. +%(distro)s will use this information for diagnostic purposes ONLY +and it will be considered confidential information. + +This process may take a while to complete. +No changes will be made to your system. + +""") + + distro = "" + + def __init__(self): + """Subclasses that choose to override this initializer should call + super() to ensure that they get the required platform bits attached. + super(SubClass, self).__init__()""" + self._parse_uname() + self.reportName = self.hostname + self.ticketNumber = None + self.package_manager = PackageManager() + + def check(self): + """ + This function is responsible for determining if the underlying system + is supported by this policy. + """ + return False + + def preferedArchive(self): + """ + Return the class object of the prefered archive format for this platform + """ + from sos.utilities import TarFileArchive + return TarFileArchive + + def getArchiveName(self): + """ + This function should return the filename of the archive without the + extension. + """ + if self.ticketNumber: + self.reportName += "." + self.ticketNumber + return "sosreport-%s-%s" % (self.reportName, time.strftime("%Y%m%d%H%M%S")) + + def validatePlugin(self, plugin_class): + """ + Verifies that the plugin_class should execute under this policy + """ + return issubclass(plugin_class, IndependentPlugin) + + def preWork(self): + """ + This function is called prior to collection. + """ + pass + + def packageResults(self, package_name): + """ + This function is called prior to packaging. + """ + pass + + def postWork(self): + """ + This function is called after the sosreport has been generated. + """ + pass + + def pkgByName(self, pkg): + return None + + def _parse_uname(self): + (system, node, release, + version, machine, processor) = platform.uname() + self.hostname = node + self.release = release + self.smp = version.split()[1] == "SMP" + self.machine = machine + + def setCommons(self, commons): + self.commons = commons + + def is_root(self): + """This method should return true if the user calling the script is + considered to be a superuser""" + return (os.getuid() == 0) + + def _create_checksum(self, final_filename=None): + if not final_filename: + return False + + archive_fp = open(final_filename, 'r') + digest = hashlib.new(get_hash_name()) + digest.update(archive_fp.read()) + archive_fp.close() + return digest.hexdigest() + + + def getPreferredHashAlgorithm(self): + """Returns the string name of the hashlib-supported checksum algorithm + to use""" + return "md5" + + def displayResults(self, final_filename=None): + + # make sure a report exists + if not final_filename: + return False + + # store checksum into file + fp = open(final_filename + "." + get_hash_name(), "w") + checksum = self._create_checksum(final_filename) + if checksum: + fp.write(checksum + "\n") + fp.close() + + self._print() + self._print(_("Your sosreport has been generated and saved in:\n %s") % final_filename) + self._print() + if checksum: + self._print(_("The checksum is: ") + checksum) + self._print() + self._print(_("Please send this file to your support representative.")) + self._print() + + def uploadResults(self, final_filename): + + # make sure a report exists + if not final_filename: + return False + + self._print() + # make sure it's readable + try: + fp = open(final_filename, "r") + except: + return False + + # read ftp URL from configuration + if self.commons['cmdlineopts'].upload: + upload_url = self.commons['cmdlineopts'].upload + else: + try: + upload_url = self.commons['config'].get("general", "ftp_upload_url") + except: + self._print(_("No URL defined in config file.")) + return + + from urlparse import urlparse + url = urlparse(upload_url) + + if url[0] != "ftp": + self._print(_("Cannot upload to specified URL.")) + return + + # extract username and password from URL, if present + if url[1].find("@") > 0: + username, host = url[1].split("@", 1) + if username.find(":") > 0: + username, passwd = username.split(":", 1) + else: + passwd = None + else: + username, passwd, host = None, None, url[1] + + # extract port, if present + if host.find(":") > 0: + host, port = host.split(":", 1) + port = int(port) + else: + port = 21 + + path = url[2] + + try: + from ftplib import FTP + upload_name = os.path.basename(final_filename) + + ftp = FTP() + ftp.connect(host, port) + if username and passwd: + ftp.login(username, passwd) + else: + ftp.login() + ftp.cwd(path) + ftp.set_pasv(True) + ftp.storbinary('STOR %s' % upload_name, fp) + ftp.quit() + except Exception, e: + self._print(_("There was a problem uploading your report to Red Hat support. " + str(e))) + else: + self._print(_("Your report was successfully uploaded to %s with name:" % (upload_url,))) + self._print(" " + upload_name) + self._print() + self._print(_("Please communicate this name to your support representative.")) + self._print() + + fp.close() + + def _print(self, msg=None): + """A wrapper around print that only prints if we are not running in + silent mode""" + if not self.commons['cmdlineopts'].silent: + if msg: + print msg + else: + print + + + def get_msg(self): + """This method is used to prepare the preamble text to display to + the user in non-batch mode. If your policy sets self.distro that + text will be substituted accordingly. You can also override this + method to do something more complicated.""" + return self.msg % {'distro': self.distro} diff --git a/sos/policies/osx.py b/sos/policies/osx.py new file mode 100644 index 00000000..60b7f6a9 --- /dev/null +++ b/sos/policies/osx.py @@ -0,0 +1,13 @@ +from sos.policies import PackageManager, Policy +from sos.utilities import shell_out + +class OSXPolicy(Policy): + + distro = "Mac OS X" + + @classmethod + def check(class_): + try: + return "Mac OS X" in shell_out("sw_vers") + except Exception, e: + return False diff --git a/sos/policies/redhat.py b/sos/policies/redhat.py new file mode 100644 index 00000000..cfd31ef2 --- /dev/null +++ b/sos/policies/redhat.py @@ -0,0 +1,229 @@ +## Implement policies required for the sos system support tool + +## Copyright (C) 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. + +# This enables the use of with syntax in python 2.5 (e.g. jython) +from __future__ import with_statement + +import os +import sys +from tempfile import gettempdir +import random +import re +import platform +import time +from collections import deque + +from sos import _sos as _ +from sos.plugins import RedHatPlugin, IndependentPlugin +from sos.policies import Policy, PackageManager +from sos.utilities import shell_out + +sys.path.insert(0, "/usr/share/rhn/") +try: + from up2date_client import up2dateAuth + from up2date_client import config + from rhn import rpclib +except: + # might fail if non-RHEL + pass + + +class RHELPackageManager(PackageManager): + + def _get_rpm_list(self): + pkg_list = shell_out(["rpm", + "-qa", + "--queryformat", + "%{NAME}|%{VERSION}\\n"]).splitlines() + self._rpms = {} + for pkg in pkg_list: + name, version = pkg.split("|") + self._rpms[name] = { + 'name': name, + 'version': version + } + + def allPkgsByName(self, name): + return fnmatch.filter(self.allPkgs().keys(), name) + + def allPkgsByNameRegex(self, regex_name, flags=None): + reg = re.compile(regex_name, flags) + return [pkg for pkg in self.allPkgs().keys() if reg.match(pkg)] + + def pkgByName(self, name): + try: + self.AllPkgsByName(name)[-1] + except Exception: + return None + + def allPkgs(self): + if not self._rpms: + self._rpms = self._get_rpm_list() + return self._rpms + + def pkgNVRA(self, pkg): + fields = pkg.split("-") + version, release, arch = fields[-3:] + name = "-".join(fields[:-3]) + return (name, version, release, arch) + + +class RHELPolicy(Policy): + + def __init__(self): + super(RHELPolicy, self).__init__() + self.reportName = "" + self.ticketNumber = "" + self.package_manager = RHELPackageManager() + + def validatePlugin(self, plugin_class): + "Checks that the plugin will execute given the environment" + return issubclass(plugin_class, RedHatPlugin) or issubclass(plugin_class, IndependentPlugin) + + @classmethod + def check(self): + "This method checks to see if we are running on RHEL. It returns True or False." + return os.path.isfile('/etc/redhat-release') + + def preferedArchive(self): + from sos.utilities import TarFileArchive + return TarFileArchive + + def getPreferredHashAlgorithm(self): + checksum = "md5" + try: + fp = open("/proc/sys/crypto/fips_enabled", "r") + except: + return checksum + + fips_enabled = fp.read() + if fips_enabled.find("1") >= 0: + checksum = "sha256" + fp.close() + return checksum + + def pkgByName(self, name): + return self.package_manager.pkgByName(name) + + def runlevelByService(self, name): + from subprocess import Popen, PIPE + ret = [] + p = Popen("LC_ALL=C /sbin/chkconfig --list %s" % name, + shell=True, + stdout=PIPE, + stderr=PIPE, + bufsize=-1) + out, err = p.communicate() + if err: + return ret + for tabs in out.split()[1:]: + try: + (runlevel, onoff) = tabs.split(":", 1) + except: + pass + else: + if onoff == "on": + ret.append(int(runlevel)) + return ret + + def runlevelDefault(self): + try: + with open("/etc/inittab") as fp: + pattern = r"id:(\d{1}):initdefault:" + text = fp.read() + return int(re.findall(pattern, text)[0]) + except: + return 3 + + def kernelVersion(self): + return self.release + + def hostName(self): + return self.hostname + + def rhelVersion(self): + try: + pkg = self.pkgByName("redhat-release") or \ + self.allPkgsByNameRegex("redhat-release-.*")[-1] + pkgname = pkg["version"] + if pkgname[0] == "4": + return 4 + elif pkgname in [ "5Server", "5Client" ]: + return 5 + elif pkgname[0] == "6": + return 6 + except: + pass + return False + + def rhnUsername(self): + try: + cfg = config.initUp2dateConfig() + + return rpclib.xmlrpclib.loads(up2dateAuth.getSystemId())[0][0]['username'] + except: + # ignore any exception and return an empty username + return "" + + def isKernelSMP(self): + return self.smp + + def getArch(self): + return self.machine + + def preWork(self): + # this method will be called before the gathering begins + + localname = self.rhnUsername() + if len(localname) == 0: localname = self.hostName() + + if not self.commons['cmdlineopts'].batch and not self.commons['cmdlineopts'].silent: + try: + self.reportName = raw_input(_("Please enter your first initial and last name [%s]: ") % localname) + self.reportName = re.sub(r"[^a-zA-Z.0-9]", "", self.reportName) + + self.ticketNumber = raw_input(_("Please enter the case number that you are generating this report for: ")) + self.ticketNumber = re.sub(r"[^0-9]", "", self.ticketNumber) + self._print() + except: + self._print() + sys.exit(0) + + if len(self.reportName) == 0: + self.reportName = localname + + if self.commons['cmdlineopts'].customerName: + self.reportName = self.commons['cmdlineopts'].customerName + self.reportName = re.sub(r"[^a-zA-Z.0-9]", "", self.reportName) + + if self.commons['cmdlineopts'].ticketNumber: + self.ticketNumber = self.commons['cmdlineopts'].ticketNumber + self.ticketNumber = re.sub(r"[^0-9]", "", self.ticketNumber) + + return + + def packageResults(self, archive_filename): + self._print(_("Creating compressed archive...")) + + def get_msg(self): + msg_dict = {"distro": "Red Hat Enterprise Linux"} + if os.path.isfile('/etc/fedora-release'): + msg_dict['distro'] = 'Fedora' + return self.msg % msg_dict + +# vim: ts=4 sw=4 et diff --git a/sos/policies/windows.py b/sos/policies/windows.py new file mode 100644 index 00000000..64e780bf --- /dev/null +++ b/sos/policies/windows.py @@ -0,0 +1,41 @@ +### 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. +import os +import time + +from sos.policies import PackageManager, Policy +from sos.utilities import shell_out + +class WindowsPolicy(Policy): + + distro = "Microsoft Windows" + + @classmethod + def check(class_): + try: + return "Windows" in shell_out("ver") + except Exception, e: + return False + + def is_root(self): + if "S-1-16-12288" in shell_out("whoami /groups"): + return True + else: + admins = shell_out("net localgroup administrators") + username = shell_out("echo %USERNAME%") + return username.strip() in admins + + def preferedArchive(self): + from sos.utilities import ZipFileArchive + return ZipFileArchive diff --git a/sos/policyredhat.py b/sos/policyredhat.py deleted file mode 100755 index 863445ae..00000000 --- a/sos/policyredhat.py +++ /dev/null @@ -1,411 +0,0 @@ -## policy-redhat.py -## Implement policies required for the sos system support tool - -## Copyright (C) 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. - -import os -import sys -import string -from tempfile import gettempdir -from sos.helpers import * -import random -import re -try: - from hashlib import md5 -except ImportError: - from md5 import md5 -import rpm -import time -from subprocess import Popen, PIPE -from collections import deque -from sos import _sos as _ - -sys.path.insert(0, "/usr/share/rhn/") -try: - from up2date_client import up2dateAuth - from up2date_client import config - from rhn import rpclib -except: - # might fail if non-RHEL - pass - -#class SosError(Exception): -# def __init__(self, code, message): -# self.code = code -# self.message = message -# -# def __str__(self): -# return 'Sos Error %s: %s' % (self.code, self.message) - -def memoized(function): - ''' function decorator to allow caching of return values - ''' - function.cache={} - def f(*args): - try: - return function.cache[args] - except KeyError: - result = function.cache[args] = function(*args) - return result - return f - -class SosPolicy: - "This class implements various policies for sos" - def __init__(self): - self.report_file = "" - self.report_file_ext = "" - self.report_md5 = "" - self.reportName = "" - self.ticketNumber = "" - - def setCommons(self, commons): - self.cInfo = commons - return - - def validatePlugin(self, pluginpath): - "Validates the plugin as being acceptable to run" - # return value - # TODO implement this - #print "validating %s" % pluginpath - return True - - def pkgProvides(self, name): - return self.pkgByName(name).get('providename') - - def pkgRequires(self, name): - return self.pkgByName(name).get('requirename') - - def allPkgsByName(self, name): - return self.allPkgs("name", name) - - def allPkgsByNameRegex(self, regex_name): - reg = re.compile(regex_name) - return [pkg for pkg in self.allPkgs() if reg.match(pkg['name'])] - - def pkgByName(self, name): - # TODO: do a full NEVRA compare and return newest version, best arch - try: - # lame attempt at locating newest - return self.allPkgsByName(name)[-1] - except: - pass - return {} - - def allPkgs(self, ds = None, value = None): - # if possible return the cached values - try: return self._cache_rpm[ "%s-%s" % (ds,value) ] - except AttributeError: self._cache_rpm = {} - except KeyError: pass - - ts = rpm.TransactionSet() - if ds and value: - mi = ts.dbMatch(ds, value) - else: - mi = ts.dbMatch() - - self._cache_rpm[ "%s-%s" % (ds,value) ] = [pkg for pkg in mi] - del mi, ts - return self._cache_rpm[ "%s-%s" % (ds,value) ] - - def runlevelByService(self, name): - ret = [] - p = Popen("LC_ALL=C /sbin/chkconfig --list %s" % name, shell=True, stdout=PIPE, stderr=PIPE, bufsize=-1) - out, err = p.communicate() - if err: - return ret - for tabs in out.split()[1:]: - try: - (runlevel, onoff) = tabs.split(":", 1) - except: - pass - else: - if onoff == "on": - ret.append(int(runlevel)) - return ret - - def runlevelDefault(self): - try: - reg=self.doRegexFindAll(r"^id:(\d{1}):initdefault:", "/etc/inittab") - for initlevel in reg: - return initlevel - except: - return 3 - - def kernelVersion(self): - return Popen("/bin/uname -r", shell=True, stdout=PIPE, bufsize=-1).stdout.read().strip("\n") - - def hostName(self): - return Popen("/bin/hostname", shell=True, stdout=PIPE, bufsize=-1).stdout.read().strip("\n").split(".")[0] - - def rhelVersion(self): - try: - pkg = self.pkgByName("redhat-release") or \ - self.allPkgsByNameRegex("redhat-release-.*")[-1] - pkgname = pkg["version"] - if pkgname[0] == "4": - return 4 - elif pkgname in [ "5Server", "5Client" ]: - return 5 - elif pkgname[0] == "6": - return 6 - except: pass - return False - - def rhnUsername(self): - try: - cfg = config.initUp2dateConfig() - - return rpclib.xmlrpclib.loads(up2dateAuth.getSystemId())[0][0]['username'] - except: - # ignore any exception and return an empty username - return "" - - def isKernelSMP(self): - pipe = Popen("/bin/hostname", shell=True, stdout=PIPE, bufsize=-1).read().stdout - if pipe.split()[1] == "SMP": - return True - else: - return False - - def getArch(self): - return Popen("/bin/uname -m", shell=True, stdout=PIPE, bufsize=-1).stdout.read().strip() - - def pkgNVRA(self, pkg): - fields = pkg.split("-") - version, release, arch = fields[-3:] - name = "-".join(fields[:-3]) - return (name, version, release, arch) - - def getDstroot(self, tmpdir='/tmp'): - """Find a temp directory to form the root for our gathered information - and reports. - """ - uniqname = "%s-%s" % (self.hostName(), time.strftime("%Y%m%d%H%M%s")) - dstroot = os.path.join(os.path.abspath(tmpdir),uniqname) - try: - os.makedirs(dstroot, 0700) - except: - return False - return dstroot - - def preWork(self): - # this method will be called before the gathering begins - - localname = self.rhnUsername() - if len(localname) == 0: localname = self.hostName() - - if not self.cInfo['cmdlineopts'].batch: - try: - self.reportName = raw_input(_("Please enter your first initial and last name [%s]: ") % localname) - self.reportName = re.sub(r"[^a-zA-Z.0-9]", "", self.reportName) - - self.ticketNumber = raw_input(_("Please enter the case number that you are generating this report for: ")) - self.ticketNumber = re.sub(r"[^0-9]", "", self.ticketNumber) - print - except: - print - sys.exit(0) - - if len(self.reportName) == 0: - self.reportName = localname - - if self.cInfo['cmdlineopts'].customerName: - self.reportName = self.cInfo['cmdlineopts'].customerName - self.reportName = re.sub(r"[^a-zA-Z.0-9]", "", self.reportName) - - if self.cInfo['cmdlineopts'].ticketNumber: - self.ticketNumber = self.cInfo['cmdlineopts'].ticketNumber - self.ticketNumber = re.sub(r"[^0-9]", "", self.ticketNumber) - - return - - def renameResults(self, newName): - newName = os.path.join(os.path.dirname(self.cInfo['dstroot']), newName) - if len(self.report_file) and os.path.isfile(self.report_file): - try: - os.rename(self.report_file, newName) - except: - return False - self.report_file = newName - - def packageResults(self): - - if len(self.ticketNumber): - self.reportName = self.reportName + "." + self.ticketNumber - else: - self.reportName = self.reportName - - curwd = os.getcwd() - os.chdir(os.path.dirname(self.cInfo['dstroot'])) - oldmask = os.umask(077) - - print _("Creating compressed archive...") - - if os.path.isfile("/usr/bin/xz"): - self.report_file_ext = "tar.xz" - self.renameResults("sosreport-%s-%s.%s" % (self.reportName, time.strftime("%Y%m%d%H%M%S"), self.report_file_ext)) - cmd = "/bin/tar -c %s | /usr/bin/xz -1 > %s" % (os.path.basename(self.cInfo['dstroot']),self.report_file) - p = Popen(cmd, shell=True, bufsize=-1) - sts = os.waitpid(p.pid, 0)[1] - else: - self.report_file_ext = "tar.bz2" - self.renameResults("sosreport-%s-%s.%s" % (self.reportName, time.strftime("%Y%m%d%H%M%S"), self.report_file_ext)) - tarcmd = "/bin/tar -jcf %s %s" % (self.report_file, os.path.basename(self.cInfo['dstroot'])) - p = Popen(tarcmd, shell=True, stdout=PIPE, stderr=PIPE, bufsize=-1) - output = p.communicate()[0] - - os.umask(oldmask) - os.chdir(curwd) - return - - def cleanDstroot(self): - if not os.path.isdir(os.path.join(self.cInfo['dstroot'],"sos_commands")): - # doesn't look like a dstroot, refusing to clean - return False - os.system("/bin/rm -rf %s" % self.cInfo['dstroot']) - - def encryptResults(self): - # make sure a report exists - if not self.report_file: - return False - - print _("Encrypting archive...") - gpgname = self.report_file + ".gpg" - - try: - keyring = self.cInfo['config'].get("general", "gpg_keyring") - except: - keyring = "/usr/share/sos/rhsupport.pub" - - try: - recipient = self.cInfo['config'].get("general", "gpg_recipient") - except: - recipient = "support@redhat.com" - - p = Popen("""/usr/bin/gpg --trust-model always --batch --keyring "%s" --no-default-keyring --compress-level 0 --encrypt --recipient "%s" --output "%s" "%s" """ % (keyring, recipient, gpgname, self.report_file), - shell=True, stdout=PIPE, stderr=PIPE, bufsize=-1) - stdout, stderr = p.communicate() - if p.returncode == 0: - os.unlink(self.report_file) - self.report_file = gpgname - else: - print _("There was a problem encrypting your report.") - sys.exit(1) - - def displayResults(self): - # make sure a report exists - if not self.report_file: - return False - - # calculate md5 - fp = open(self.report_file, "r") - self.report_md5 = md5(fp.read()).hexdigest() - fp.close() - - self.renameResults("sosreport-%s-%s-%s.%s" % (self.reportName, - time.strftime("%Y%m%d%H%M%S"), - self.report_md5[-4:], - self.report_file_ext)) - - # store md5 into file - fp = open(self.report_file + ".md5", "w") - fp.write(self.report_md5 + "\n") - fp.close() - - print - print _("Your sosreport has been generated and saved in:\n %s") % self.report_file - print - if len(self.report_md5): - print _("The md5sum is: ") + self.report_md5 - print - print _("Please send this file to your support representative.") - print - - def uploadResults(self): - # make sure a report exists - if not self.report_file: - return False - - print - # make sure it's readable - try: - fp = open(self.report_file, "r") - except: - return False - - # read ftp URL from configuration - if self.cInfo['cmdlineopts'].upload: - upload_url = self.cInfo['cmdlineopts'].upload - else: - try: - upload_url = self.cInfo['config'].get("general", "ftp_upload_url") - except: - print _("No URL defined in config file.") - return - - from urlparse import urlparse - url = urlparse(upload_url) - - if url[0] != "ftp": - print _("Cannot upload to specified URL.") - return - - # extract username and password from URL, if present - if url[1].find("@") > 0: - username, host = url[1].split("@", 1) - if username.find(":") > 0: - username, passwd = username.split(":", 1) - else: - passwd = None - else: - username, passwd, host = None, None, url[1] - - # extract port, if present - if host.find(":") > 0: - host, port = host.split(":", 1) - port = int(port) - else: - port = 21 - - path = url[2] - - try: - from ftplib import FTP - upload_name = os.path.basename(self.report_file) - - ftp = FTP() - ftp.connect(host, port) - if username and passwd: - ftp.login(username, passwd) - else: - ftp.login() - ftp.cwd(path) - ftp.set_pasv(True) - ftp.storbinary('STOR %s' % upload_name, fp) - ftp.quit() - except: - print _("There was a problem uploading your report to Red Hat support.") - else: - print _("Your report was successfully uploaded to %s with name:" % (upload_url,)) - print " " + upload_name - print - print _("Please communicate this name to your support representative.") - print - - fp.close() - -# vim: ts=4 sw=4 et diff --git a/sos/reporting.py b/sos/reporting.py new file mode 100644 index 00000000..bf5addfa --- /dev/null +++ b/sos/reporting.py @@ -0,0 +1,131 @@ +"""This provides a restricted tag language to define the sosreport index/report""" + +try: + import json +except ImportError: + import simplejson as json + + +class Node(object): + + def __str__(self): + return json.dumps(self.data) + + def can_add(self, node): + return False + +class Leaf(Node): + """Marker class that can be added to a Section node""" + pass + + +class Report(Node): + """The root element of a report. This is a container for sections.""" + + def __init__(self): + self.data = {} + + def can_add(self, node): + return isinstance(node, Section) + + def add(self, *nodes): + for node in nodes: + if self.can_add(node): + self.data[node.name] = node.data + + +class Section(Node): + """A section is a container for leaf elements. Sections may be nested + inside of Report objects only.""" + + def __init__(self, name): + self.name = name + self.data = {} + + def can_add(self, node): + return isinstance(node, Leaf) + + def add(self, *nodes): + for node in nodes: + if self.can_add(node): + self.data.setdefault(node.ADDS_TO, []).append(node.data) + + +class Command(Leaf): + + ADDS_TO = "commands" + + def __init__(self, name, return_code, href): + self.data = {"name": name, + "return_code": return_code, + "href": href} + + +class CopiedFile(Leaf): + + ADDS_TO = "copied_files" + + def __init__(self, name, href): + self.data = {"name": name, + "href": href} + + +class CreatedFile(Leaf): + + ADDS_TO = "created_files" + + def __init__(self, name): + self.data = {"name": name} + + +class Alert(Leaf): + + ADDS_TO = "alerts" + + def __init__(self, content): + self.data = content + + +class Note(Leaf): + + ADDS_TO = "notes" + + def __init__(self, content): + self.data = content + + +class PlainTextReport(object): + """Will generate a plain text report from a top_level Report object""" + + LEAF = " * %(name)s" + ALERT = " ! %s" + NOTE = " * %s" + DIVIDER = "=" * 72 + + subsections = ( + (Command, LEAF, "- commands executed:"), + (CopiedFile, LEAF, "- files copied:"), + (CreatedFile, LEAF, "- files created:"), + (Alert, ALERT, "- alerts:"), + (Note, NOTE, "- notes:"), + ) + + buf = [] + + def __init__(self, report_node): + self.report_node = report_node + + def __str__(self): + self.buf = buf = [] + for section_name, section_contents in sorted(self.report_node.data.iteritems()): + buf.append(section_name + "\n" + self.DIVIDER) + for type_, format_, header in self.subsections: + self.process_subsection(section_contents, type_.ADDS_TO, header, format_) + + return "\n".join(buf) + + def process_subsection(self, section, key, header, format_): + if key in section: + self.buf.append(header) + for item in section.get(key): + self.buf.append(format_ % item) diff --git a/sos/sosreport.py b/sos/sosreport.py index 45103ec9..08f11fd6 100755..100644 --- a/sos/sosreport.py +++ b/sos/sosreport.py @@ -32,76 +32,52 @@ supplied for application-specific information # pylint: disable-msg = R0904 # pylint: disable-msg = R0903 -import sys, traceback +import sys +import traceback import os import logging from optparse import OptionParser, Option import ConfigParser -import sos.policyredhat -from sos.helpers import importPlugin -import signal +from sos.plugins import import_plugin +from sos.utilities import ImporterHelper from stat import ST_UID, ST_GID, ST_MODE, ST_CTIME, ST_ATIME, ST_MTIME, S_IMODE from time import strftime, localtime from collections import deque from itertools import izip +import textwrap +import tempfile from sos import _sos as _ from sos import __version__ +import sos.policies +from sos.utilities import TarFileArchive, ZipFileArchive, compress +from sos.reporting import Report, Section, Command, CopiedFile, CreatedFile, Alert, Note, PlainTextReport -if os.path.isfile('/etc/fedora-release'): - __distro__ = 'Fedora' -else: - __distro__ = 'Red Hat Enterprise Linux' +class TempFileUtil(object): -class GlobalVars: - """ Generic container for shared vars """ - def __init__(self): - pass - -## Set up routines to be linked to signals for termination handling -def exittermhandler(signum, frame): - """ Handle signals cleanly """ - del frame, signum - doExitCode() - -def doExitCode(): - """ Exit with return """ - - for plugname, plug in GlobalVars.loadedplugins: - plug.exit_please() - del plugname - - print "All processes ended, cleaning up." - doExit(1) - -def doExit(error=0): - """ Exit with return """ - # We will attempt to clean dstroot; there is only - # one instance where the policy is not set and that is - # during the actual creation of dstroot - try: - GlobalVars.policy.cleanDstroot() - except AttributeError: - sys.exit(error) - sys.exit(error) - -def doException(etype, eval, etrace): - """ Wrap exception in debugger if not in tty """ - if hasattr(sys, 'ps1') or not sys.stderr.isatty(): - # we are in interactive mode or we don't have a tty-like - # device, so we call the default hook - sys.__excepthook__(etype, eval, etrace) - else: - import traceback, pdb - # we are NOT in interactive mode, print the exception... - traceback.print_exception(etype, eval, etrace, limit=2, file=sys.stdout) - print - # ...then start the debugger in post-mortem mode. - pdb.pm() + def __init__(self, tmp_dir): + self.tmp_dir = tmp_dir + self.files = [] + + def new(self): + fd, fname = tempfile.mkstemp(dir=self.tmp_dir) + fobj = open(fname, 'w') + self.files.append((fname, fobj)) + return fobj + + def clean(self): + for fname, f in self.files: + try: + f.flush() + f.close() + except Exception, e: + pass + try: + os.unlink(fname) + except Exception, e: + pass + self.files = [] -# Handle any sort of exit signal cleanly -# Currently, we intercept only sig 15 (TERM) -signal.signal(signal.SIGTERM, exittermhandler) class OptionParserExtended(OptionParser): """ Show examples """ @@ -117,9 +93,8 @@ class OptionParserExtended(OptionParser): print " disable memory and samba plugins, turn off rpm -Va collection:" print " # sosreport -n memory,samba -k rpm.rpmva=off" print - del out -class SosOption (Option): +class SosOption(Option): """Allow to specify comma delimited list of plugins""" ACTIONS = Option.ACTIONS + ("extend",) STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",) @@ -137,92 +112,9 @@ class SosOption (Option): else: Option.take_action(self, action, dest, opt, value, values, parser) -def parse_options(opts): - """ Parse command line options """ - - __cmdParser__ = OptionParserExtended(option_class=SosOption) - __cmdParser__.add_option("-l", "--list-plugins", action="store_true", \ - dest="listPlugins", default=False, \ - help="list plugins and available plugin options") - __cmdParser__.add_option("-n", "--skip-plugins", action="extend", \ - dest="noplugins", type="string", \ - help="skip these plugins", default = deque()) - __cmdParser__.add_option("-e", "--enable-plugins", action="extend", \ - dest="enableplugins", type="string", \ - help="enable these plugins", default = deque()) - __cmdParser__.add_option("-o", "--only-plugins", action="extend", \ - dest="onlyplugins", type="string", \ - help="enable these plugins only", default = deque()) - __cmdParser__.add_option("-k", action="extend", \ - dest="plugopts", type="string", \ - help="plugin options in plugname.option=value format (see -l)") - __cmdParser__.add_option("-a", "--alloptions", action="store_true", \ - dest="usealloptions", default=False, \ - help="enable all options for loaded plugins") - __cmdParser__.add_option("-u", "--upload", action="store", \ - dest="upload", default=False, \ - help="upload the report to an ftp server") - #__cmdParser__.add_option("--encrypt", action="store_true", \ - # dest="encrypt", default=False, \ - # help="encrypt with GPG using Red Hat support's public key") - __cmdParser__.add_option("--batch", action="store_true", \ - dest="batch", default=False, \ - help="do not ask any question (batch mode)") - __cmdParser__.add_option("--build", action="store_true", \ - dest="build", default=False, \ - help="keep sos tree available and dont package results") - __cmdParser__.add_option("--no-colors", action="store_true", \ - dest="nocolors", default=False, \ - help="do not use terminal colors for text") - __cmdParser__.add_option("-v", "--verbose", action="count", \ - dest="verbosity", \ - help="increase verbosity") - __cmdParser__.add_option("--debug", action="count", \ - dest="debug", \ - help="enabling debugging through python debugger") - __cmdParser__.add_option("--ticket-number", action="store", \ - dest="ticketNumber", \ - help="set ticket number") - __cmdParser__.add_option("--name", action="store", \ - dest="customerName", \ - help="define customer name") - __cmdParser__.add_option("--config-file", action="store", \ - dest="config_file", \ - help="specify alternate configuration file") - __cmdParser__.add_option("--tmp-dir", action="store", \ - dest="tmp_dir", \ - help="specify alternate temporary directory", default="/tmp") - __cmdParser__.add_option("--diagnose", action="store_true", \ - dest="diagnose", \ - help="enable diagnostics", default=False) - __cmdParser__.add_option("--analyze", action="store_true", \ - dest="analyze", \ - help="enable analyzations", default=False) - __cmdParser__.add_option("--report", action="store_true", \ - dest="report", \ - help="Enable html/xml reporting", default=False) - __cmdParser__.add_option("--profile", action="store_true", \ - dest="profiler", \ - help="turn on profiling", default=False) - - (GlobalVars.__cmdLineOpts__, GlobalVars.__cmdLineArgs__) = __cmdParser__.parse_args(opts) - -def textcolor(text, color, raw=0): - """ Terminal text coloring function """ - if GlobalVars.__cmdLineOpts__.nocolors or not sys.stdout.isatty(): - return text - colors = { "black":"30", "red":"31", "green":"32", "brown":"33", "blue":"34", - "purple":"35", "cyan":"36", "lgray":"37", "gray":"1;30", "lred":"1;31", - "lgreen":"1;32", "yellow":"1;33", "lblue":"1;34", "pink":"1;35", - "lcyan":"1;36", "white":"1;37" } - opencol = "\033[" - closecol = "m" - clear = opencol + "0" + closecol - f = opencol + colors[color] + closecol - del raw - return "%s%s%s" % (f, text, clear) - -class XmlReport: + + +class XmlReport(object): """ Report build class """ def __init__(self): try: @@ -290,474 +182,550 @@ class XmlReport: if not self.enabled: return - print self.doc.serialize(None, 1) + self.ui_log.info(self.doc.serialize(None, 1)) def serialize_to_file(self, fname): """ Serializes to file """ if not self.enabled: return - outfn = open(fname,"w") - outfn.write(self.doc.serialize(None, 1)) - outfn.close() - -# if debugging is enabled, allow plugins to raise exceptions -def isDebug(): - """ Enable plugin to raise exception """ - if GlobalVars.__cmdLineOpts__.debug: - sys.excepthook = doException - GlobalVars.__raisePlugins__ = 1 - else: - GlobalVars.__raisePlugins__ = 0 - -def sosreport(opts): - """ - This is the top-level function that gathers - and processes all sosreport information - """ - parse_options(opts) - # check debug - isDebug() - - config = ConfigParser.ConfigParser() - if GlobalVars.__cmdLineOpts__.config_file: - config_file = GlobalVars.__cmdLineOpts__.config_file - else: - config_file = '/etc/sos.conf' - try: - config.readfp(open(config_file)) - except IOError: - pass - - GlobalVars.loadedplugins = deque() - skippedplugins = deque() - alloptions = deque() - - # perhaps we should automatically locate the policy module?? - GlobalVars.policy = sos.policyredhat.SosPolicy() - - # find the plugins path - paths = sys.path - for path in paths: - if path.strip()[-len("site-packages"):] == "site-packages" \ - and os.path.isdir(path + "/sos/plugins"): - pluginpath = path + "/sos/plugins" - - # Set up common info and create destinations - - GlobalVars.dstroot = GlobalVars.policy.getDstroot(GlobalVars.__cmdLineOpts__.tmp_dir) - if not GlobalVars.dstroot: - print _("Could not create temporary directory.") - doExit() - - cmddir = os.path.join(GlobalVars.dstroot, "sos_commands") - logdir = os.path.join(GlobalVars.dstroot, "sos_logs") - rptdir = os.path.join(GlobalVars.dstroot, "sos_reports") - os.mkdir(cmddir, 0755) - os.mkdir(logdir, 0755) - os.mkdir(rptdir, 0755) - - # initialize logging - soslog = logging.getLogger('sos') - soslog.setLevel(logging.DEBUG) - - logging.VERBOSE = logging.INFO - 1 - logging.VERBOSE2 = logging.INFO - 2 - logging.VERBOSE3 = logging.INFO - 3 - logging.addLevelName(logging.VERBOSE, "verbose") - logging.addLevelName(logging.VERBOSE2,"verbose2") - logging.addLevelName(logging.VERBOSE3,"verbose3") - - if GlobalVars.__cmdLineOpts__.profiler: - proflog = logging.getLogger('sosprofile') - proflog.setLevel(logging.DEBUG) - - # if stdin is not a tty, disable colors and don't ask questions - if not sys.stdin.isatty(): - GlobalVars.__cmdLineOpts__.nocolors = True - GlobalVars.__cmdLineOpts__.batch = True - - # log to a file - flog = logging.FileHandler(logdir + "/sos.log") - flog.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s')) - flog.setLevel(logging.VERBOSE3) - soslog.addHandler(flog) - - if GlobalVars.__cmdLineOpts__.profiler: - # setup profile log - plog = logging.FileHandler(logdir + "/sosprofile.log") - plog.setFormatter(logging.Formatter('%(message)s')) - plog.setLevel(logging.DEBUG) - proflog.addHandler(plog) - - # define a Handler which writes INFO messages or higher to the sys.stderr - console = logging.StreamHandler(sys.stderr) - if GlobalVars.__cmdLineOpts__.verbosity > 0: - console.setLevel(20 - GlobalVars.__cmdLineOpts__.verbosity) - else: - console.setLevel(logging.INFO) - console.setFormatter(logging.Formatter('%(message)s')) - soslog.addHandler(console) - - xmlrep = XmlReport() - - # set up dict so everyone can share the following - commons = {'dstroot': GlobalVars.dstroot, 'cmddir': cmddir, 'logdir': logdir, 'rptdir': rptdir, - 'soslog': soslog, 'policy': GlobalVars.policy, 'verbosity' : GlobalVars.__cmdLineOpts__.verbosity, - 'xmlreport' : xmlrep, 'cmdlineopts':GlobalVars.__cmdLineOpts__, 'config':config } - - # Make policy aware of the commons - GlobalVars.policy.setCommons(commons) - - print - print _("sosreport (version %s)" % (__version__,)) - print - - # disable plugins that we read from conf files - conf_disable_plugins_list = deque() - conf_disable_plugins = None - if config.has_option("plugins", "disable"): - conf_disable_plugins = config.get("plugins", "disable").split(',') - for item in conf_disable_plugins: - conf_disable_plugins_list.append(item.strip()) - - # generate list of available plugins - plugins = os.listdir(pluginpath) - plugins.sort() - plugin_names = deque() - - # validate and load plugins - for plug in plugins: - plugbase = plug[:-3] - if not plug[-3:] == '.py' or plugbase == "__init__": - continue - try: - if GlobalVars.policy.validatePlugin(pluginpath + plug): - pluginClass = importPlugin("sos.plugins." + plugbase, plugbase) - else: - soslog.warning(_("plugin %s does not validate, skipping") % plug) - skippedplugins.append((plugbase, pluginClass(plugbase, commons))) - continue - # plug-in is valid, let's decide whether run it or not - plugin_names.append(plugbase) - if plugbase in GlobalVars.__cmdLineOpts__.noplugins or \ - plugbase in conf_disable_plugins_list: - # skipped - skippedplugins.append((plugbase, pluginClass(plugbase, commons))) - continue - if not pluginClass(plugbase, commons).checkenabled() and \ - not plugbase in GlobalVars.__cmdLineOpts__.enableplugins and \ - not plugbase in GlobalVars.__cmdLineOpts__.onlyplugins: - # inactive - skippedplugins.append((plugbase, pluginClass(plugbase, commons))) - continue - if not pluginClass(plugbase, commons).defaultenabled() and \ - not plugbase in GlobalVars.__cmdLineOpts__.enableplugins and \ - not plugbase in GlobalVars.__cmdLineOpts__.onlyplugins: - # not loaded by default - skippedplugins.append((plugbase, pluginClass(plugbase, commons))) - continue - if GlobalVars.__cmdLineOpts__.onlyplugins and \ - not plugbase in GlobalVars.__cmdLineOpts__.onlyplugins: - # not specified - skippedplugins.append((plugbase, pluginClass(plugbase, commons))) - continue - GlobalVars.loadedplugins.append((plugbase, pluginClass(plugbase, commons))) - except: - soslog.warning(_("plugin %s does not install, skipping") % plug) - if GlobalVars.__raisePlugins__: - raise + outf = tempfile.NamedTemporaryFile() + outf.write(self.doc.serialize(None, 1)) + outf.flush() + self.archive.add_file(outf.name, dest=fname) + outf.close() - # First, gather and process options - # using the options specified in the command line (if any) - if GlobalVars.__cmdLineOpts__.usealloptions: - for plugname, plug in GlobalVars.loadedplugins: - for name, parms in zip(plug.optNames, plug.optParms): - if type(parms["enabled"])==bool: - parms["enabled"] = True - del name - - # read plugin tunables from configuration file - if config.has_section("tunables"): - if not GlobalVars.__cmdLineOpts__.plugopts: - GlobalVars.__cmdLineOpts__.plugopts = deque() - - for opt, val in config.items("tunables"): - if not opt.split('.')[0] in conf_disable_plugins_list: - GlobalVars.__cmdLineOpts__.plugopts.append(opt + "=" + val) - - if GlobalVars.__cmdLineOpts__.plugopts: - opts = {} - for opt in GlobalVars.__cmdLineOpts__.plugopts: - # split up "general.syslogsize=5" - try: - opt, val = opt.split("=") - except: - val = True - else: - if val.lower() in ["off", "disable", "disabled", "false"]: - val = False - else: - # try to convert string "val" to int() - try: - val = int(val) - except: - pass - # split up "general.syslogsize" - try: - plug, opt = opt.split(".") - except: - plug = opt - opt = True +class SoSReport(object): - try: - opts[plug] - except KeyError: - opts[plug] = deque() - opts[plug].append( (opt, val) ) - - for plugname, plug in GlobalVars.loadedplugins: - if plugname in opts: - for opt, val in opts[plugname]: - if not plug.setOption(opt, val): - soslog.error('no such option "%s" for plugin ' \ - '(%s)' % (opt,plugname)) - doExit(1) - del opts[plugname] - for plugname in opts.keys(): - soslog.error('unable to set option for disabled or non-existing ' \ - 'plugin (%s)' % (plugname)) - # Do not want to exit on invalid opts due to a misconfiguration in sos.conf - # doExit(1) - del opt, opts, val - - # error if the user references a plugin which does not exist - unk_plugs = [plugname.split(".")[0] for plugname in \ - GlobalVars.__cmdLineOpts__.onlyplugins \ - if not plugname.split(".")[0] in plugin_names] - unk_plugs += [plugname.split(".")[0] for plugname in \ - GlobalVars.__cmdLineOpts__.noplugins \ - if not plugname.split(".")[0] in plugin_names] - unk_plugs += [plugname.split(".")[0] for plugname in \ - GlobalVars.__cmdLineOpts__.enableplugins \ - if not plugname.split(".")[0] in plugin_names] - if len(unk_plugs): - for plugname in unk_plugs: - soslog.error('a non-existing plugin (%s) was specified in the ' \ - 'command line' % (plugname)) - doExit(1) - del unk_plugs - - for plugname, plug in GlobalVars.loadedplugins: - names, parms = plug.getAllOptions() - for optname, optparm in zip(names, parms): - alloptions.append((plug, plugname, optname, optparm)) - - # when --listplugins is specified we do a dry-run - # which tells the user which plugins are going to be enabled - # and with what options. - - if GlobalVars.__cmdLineOpts__.listPlugins: - if not len(GlobalVars.loadedplugins) and not len(skippedplugins): - soslog.error(_("no valid plugins found")) - doExit(1) - - if len(GlobalVars.loadedplugins): - print _("The following plugins are currently enabled:") - print - for (plugname, plug) in GlobalVars.loadedplugins: - print " %-25s %s" % (textcolor(plugname,"lblue"), - plug.get_description()) + def __init__(self, opts): + self.loaded_plugins = deque() + self.skipped_plugins = deque() + self.all_options = deque() + self.xml_report = XmlReport() + self.global_plugin_options = {} + + try: + import signal + signal.signal(signal.SIGTERM, self.get_exit_handler()) + except Exception: + pass # not available in java, but we don't care + + + self.opts, self.args = self.parse_options(opts) + self.tempfile_util = TempFileUtil(tmp_dir=self.opts.tmp_dir) + self._set_debug() + self._read_config() + self.policy = sos.policies.load() + self._is_root = self.policy.is_root() + self._set_directories() + + def print_header(self): + self.ui_log.info("\n%s\n" % _("sosreport (version %s)" % (__version__,))) + + def get_commons(self): + return { + 'cmddir': self.cmddir, + 'logdir': self.logdir, + 'rptdir': self.rptdir, + 'soslog': self.soslog, + 'policy': self.policy, + 'verbosity': self.opts.verbosity, + 'xmlreport': self.xml_report, + 'cmdlineopts': self.opts, + 'config': self.config, + 'global_plugin_options': self.global_plugin_options, + } + + def get_temp_file(self): + return self.tempfile_util.new() + + def _set_archive(self): + if self.opts.compression_type not in ('auto', 'zip', 'bzip2', 'gzip', 'xz'): + raise Exception("Invalid compression type specified. Options are: auto, zip, bzip2, gzip and xz") + archive_name = os.path.join(self.opts.tmp_dir,self.policy.getArchiveName()) + if self.opts.compression_type == 'auto': + auto_archive = self.policy.preferedArchive() + self.archive = auto_archive(archive_name) + elif self.opts.compression_type == 'zip': + self.archive = ZipFileArchive(archive_name) else: - print _("No plugin enabled.") - print + self.archive = TarFileArchive(archive_name) - if len(skippedplugins): - print _("The following plugins are currently disabled:") - print - for (plugname, plugclass) in skippedplugins: - print " %-25s %s" % (textcolor(plugname,"cyan"), - plugclass.get_description()) - print + def _set_directories(self): + self.cmddir = 'sos_commands' + self.logdir = 'sos_logs' + self.rptdir = 'sos_reports' - if len(alloptions): - print _("The following plugin options are available:") + def _set_debug(self): + if self.opts.debug: + sys.excepthook = self._exception + self.raise_plugins = True + else: + self.raise_plugins = False + + @staticmethod + def _exception(etype, eval_, etrace): + """ Wrap exception in debugger if not in tty """ + if hasattr(sys, 'ps1') or not sys.stderr.isatty(): + # we are in interactive mode or we don't have a tty-like + # device, so we call the default hook + sys.__excepthook__(etype, eval_, etrace) + else: + import traceback, pdb + # we are NOT in interactive mode, print the exception... + traceback.print_exception(etype, eval_, etrace, limit=2, file=sys.stdout) print - for (plug, plugname, optname, optparm) in alloptions: + # ...then start the debugger in post-mortem mode. + pdb.pm() + + def _exit(self, error=0): + raise SystemExit() +# sys.exit(error) + + def _exit_nice(self): + for plugname, plugin in self.loaded_plugins: + plugin.exit_please() + self.ui_log.info("All processes ended, cleaning up.") + self._exit(1) + + def get_exit_handler(self): + def exit_handler(signum, frame): + self._exit_nice() + return exit_handler + + def _read_config(self): + self.config = ConfigParser.ConfigParser() + if self.opts.config_file: + config_file = self.opts.config_file + else: + config_file = '/etc/sos.conf' + try: + self.config.readfp(open(config_file)) + except IOError: + pass + + def _setup_logging(self): + + if not sys.stdin.isatty(): + self.opts.nocolors = True + self.opts.batch = True + + # main soslog + self.soslog = logging.getLogger('sos') + self.soslog.setLevel(logging.DEBUG) + self.sos_log_file = self.get_temp_file() + self.sos_log_file.close() + flog = logging.FileHandler(self.sos_log_file.name) + flog.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s')) + flog.setLevel(logging.INFO) + self.soslog.addHandler(flog) + + if not self.opts.silent: + console = logging.StreamHandler(sys.stderr) + console.setFormatter(logging.Formatter('%(message)s')) + if self.opts.verbosity > 1: + console.setLevel(logging.DEBUG) + elif self.opts.verbosity > 0: + console.setLevel(logging.INFO) + else: + console.setLevel(logging.FATAL) + self.soslog.addHandler(console) + + # ui log + self.ui_log = logging.getLogger('sos_ui') + self.ui_log.setLevel(logging.INFO) + self.sos_ui_log_file = self.get_temp_file() + self.sos_ui_log_file.close() + ui_fhandler = logging.FileHandler(self.sos_ui_log_file.name) + ui_fhandler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s')) + + self.ui_log.addHandler(ui_fhandler) + + if not self.opts.silent: + ui_console = logging.StreamHandler(sys.stdout) + ui_console.setFormatter(logging.Formatter('%(message)s')) + ui_console.setLevel(logging.INFO) + self.ui_log.addHandler(ui_console) + + # profile logging + if self.opts.profiler: + self.proflog = logging.getLogger('sosprofile') + self.proflog.setLevel(logging.DEBUG) + self.sos_profile_log_file = self.get_temp_file() + plog = logging.FileHandler(self.sos_profile_log_file.name) + plog.setFormatter(logging.Formatter('%(message)s')) + plog.setLevel(logging.DEBUG) + self.proflog.addHandler(plog) + + def _finish_logging(self): + logging.shutdown() + + # the logging module seems to persist in the jython/jboss/eap world + # so the handlers need to be removed + for logger in [logging.getLogger(x) for x in ('sos', 'sosprofile', 'sos_ui')]: + for h in logger.handlers: + logger.removeHandler(h) + + if getattr(self, "sos_log_file", None): + self.archive.add_file(self.sos_log_file.name, dest=os.path.join('sos_logs', 'sos.log')) + if getattr(self, "sos_profile_log_file", None): + self.archive.add_file(self.sos_profile_log_file.name, dest=os.path.join('sos_logs', 'profile.log')) + if getattr(self, "sos_ui_log_file", None): + self.archive.add_file(self.sos_ui_log_file.name, dest=os.path.join('sos_logs', 'ui.log')) + + def _get_disabled_plugins(self): + disabled = [] + if self.config.has_option("plugins", "disable"): + disabled = [plugin.strip() for plugin in + self.config.get("plugins", "disable").split(',')] + return disabled + + def _is_skipped(self, plugin_name): + return (plugin_name in self.opts.noplugins or + plugin_name in self._get_disabled_plugins()) + + def _is_inactive(self, plugin_name, pluginClass): + return (not pluginClass(self.get_commons()).checkenabled() and + not plugin_name in self.opts.enableplugins and + not plugin_name in self.opts.onlyplugins) + + def _is_not_default(self, plugin_name, pluginClass): + return (not pluginClass(self.get_commons()).defaultenabled() and + not plugin_name in self.opts.enableplugins and + not plugin_name in self.opts.onlyplugins) + + def _is_not_specified(self, plugin_name): + return (self.opts.onlyplugins and + not plugin_name in self.opts.onlyplugins) + + def _skip(self, plugin_class, reason="unknown"): + self.skipped_plugins.append(( + plugin_class.name(), + plugin_class(self.get_commons()), + reason + )) + + def _load(self, plugin_class): + self.loaded_plugins.append(( + plugin_class.name(), + plugin_class(self.get_commons()) + )) + + + def load_plugins(self): + + import sos.plugins + helper = ImporterHelper(sos.plugins) + plugins = helper.get_modules() + self.plugin_names = deque() + + # validate and load plugins + for plug in plugins: + plugbase, ext = os.path.splitext(plug) + try: + plugin_classes = import_plugin(plugbase) + + for plugin_class in plugin_classes: + if not self.policy.validatePlugin(plugin_class): + self.soslog.debug(_("plugin %s does not validate, skipping") % plug) + self._skip(plugin_class, _("does not validate")) + continue + + if plugin_class.requires_root and not self._is_root: + self.soslog.debug(_("plugin %s requires root permissions to execute, skipping") % plug) + self._skip(plugin_class, _("requires root")) + continue + + # plug-in is valid, let's decide whether run it or not + self.plugin_names.append(plugbase) + + if any((self._is_skipped(plugbase), + self._is_inactive(plugbase, plugin_class), + self._is_not_default(plugbase, plugin_class), + self._is_not_specified(plugbase), + )): + self._skip(plugin_class, _("inactive")) + continue + + self._load(plugin_class) + except Exception, e: + self.soslog.warning(_("plugin %s does not install, skipping: %s") % (plug, e)) + if self.raise_plugins: + raise + + def _set_all_options(self): + if self.opts.usealloptions: + for plugname, plug in self.loaded_plugins: + for name, parms in zip(plug.optNames, plug.optParms): + if type(parms["enabled"])==bool: + parms["enabled"] = True + + def _set_tunables(self): + if self.config.has_section("tunables"): + if not self.opts.plugopts: + self.opts.plugopts = deque() + + for opt, val in self.config.items("tunables"): + if not opt.split('.')[0] in self.disabled: + self.opts.plugopts.append(opt + "=" + val) + if self.opts.plugopts: + opts = {} + for opt in self.opts.plugopts: + # split up "general.syslogsize=5" + try: + opt, val = opt.split("=") + except: + val = True + else: + if val.lower() in ["off", "disable", "disabled", "false"]: + val = False + else: + # try to convert string "val" to int() + try: + val = int(val) + except: + pass + + # split up "general.syslogsize" + try: + plug, opt = opt.split(".") + except: + plug = opt + opt = True + + try: + opts[plug] + except KeyError: + opts[plug] = deque() + opts[plug].append( (opt, val) ) + + for plugname, plug in self.loaded_plugins: + if plugname in opts: + for opt, val in opts[plugname]: + if not plug.setOption(opt, val): + self.soslog.error('no such option "%s" for plugin ' + '(%s)' % (opt,plugname)) + self._exit(1) + del opts[plugname] + for plugname in opts.keys(): + self.soslog.error('unable to set option for disabled or non-existing ' + 'plugin (%s)' % (plugname)) + + def _check_for_unknown_plugins(self): + import itertools + for plugin in itertools.chain(self.opts.onlyplugins, + self.opts.noplugins, + self.opts.enableplugins): + plugin_name = plugin.split(".")[0] + if not plugin_name in self.plugin_names: + self.soslog.fatal('a non-existing plugin (%s) was specified in the ' + 'command line' % (plugin_name)) + self._exit(1) + + def _set_plugin_options(self): + for plugin_name, plugin in self.loaded_plugins: + names, parms = plugin.getAllOptions() + for optname, optparm in zip(names, parms): + self.all_options.append((plugin, plugin_name, optname, optparm)) + + def list_plugins(self): + if not self.loaded_plugins and not self.skipped_plugins: + self.soslog.fatal(_("no valid plugins found")) + return + + if self.loaded_plugins: + self.ui_log.info(_("The following plugins are currently enabled:")) + self.ui_log.info("") + for (plugname, plug) in self.loaded_plugins: + self.ui_log.info(" %-15s %s" % (plugname, plug.get_description())) + else: + self.ui_log.info(_("No plugin enabled.")) + self.ui_log.info("") + + if self.skipped_plugins: + self.ui_log.info(_("The following plugins are currently disabled:")) + self.ui_log.info("") + for (plugname, plugclass, reason) in self.skipped_plugins: + self.ui_log.info(" %-15s %-14s %s" % (plugname, + reason, + plugclass.get_description())) + self.ui_log.info("") + + if self.all_options: + self.ui_log.info(_("The following plugin options are available:")) + self.ui_log.info("") + for (plug, plugname, optname, optparm) in self.all_options: # format and colorize option value based on its type (int or bool) if type(optparm["enabled"]) == bool: if optparm["enabled"] == True: - tmpopt = textcolor("on","lred") - else: - tmpopt = textcolor("off","red") - elif type(optparm["enabled"]) == int: - if optparm["enabled"] > 0: - tmpopt = textcolor(optparm["enabled"],"lred") + tmpopt = "on" else: - tmpopt = textcolor(optparm["enabled"],"red") + tmpopt = "off" else: tmpopt = optparm["enabled"] - print " %-21s %-5s %s" % (plugname + "." + optname, - tmpopt, optparm["desc"]) - del tmpopt + self.ui_log.info(" %-25s %-15s %s" % ( + plugname + "." + optname, tmpopt, optparm["desc"])) else: - print _("No plugin options available.") - - print - doExit() - - # to go anywhere further than listing the - # plugins we will need root permissions. - if os.getuid() != 0: - print _('sosreport requires root permissions to run.') - doExit(1) - - # we don't need to keep in memory plugins we are not going to use - del skippedplugins - - if not len(GlobalVars.loadedplugins): - soslog.error(_("no valid plugins were enabled")) - doExit(1) - - msg = _("""This utility will collect some detailed information about the -hardware and setup of your %(distroa)s system. -The information is collected and an archive is packaged under -/tmp, which you can send to a support representative. -%(distrob)s will use this information for diagnostic purposes ONLY -and it will be considered confidential information. + self.ui_log.info(_("No plugin options available.")) -This process may take a while to complete. -No changes will be made to your system. + self.ui_log.info("") -""" % {'distroa':__distro__, 'distrob':__distro__}) + def batch(self): + if self.opts.batch: + self.ui_log.info(self.policy.get_msg()) + else: + msg = self.policy.get_msg() + msg += _("Press ENTER to continue, or CTRL-C to quit.\n") + try: + raw_input(msg) + except: + self.ui_log.info("") + self._exit() - if GlobalVars.__cmdLineOpts__.batch: - print msg - else: - msg += _("""Press ENTER to continue, or CTRL-C to quit.\n""") - try: - raw_input(msg) - except: - print - doExit() - del msg + def _log_plugin_exception(self, plugin_name): + self.soslog.error("%s\n%s" % (plugin_name, traceback.format_exc())) - if GlobalVars.__cmdLineOpts__.diagnose: - # Call the diagnose() method for each plugin + def diagnose(self): tmpcount = 0 - for plugname, plug in GlobalVars.loadedplugins: + for plugname, plug in self.loaded_plugins: try: plug.diagnose() except: - if GlobalVars.__raisePlugins__: + if self.raise_plugins: raise else: - error_log = open(logdir + "/sosreport-plugin-errors.txt", "a") - etype, eval, etrace = sys.exc_info() - traceback.print_exception(etype, eval, etrace, limit=2, file=sys.stdout) - error_log.write(traceback.format_exc()) - error_log.close() + self._log_plugin_exception(plugname) tmpcount += len(plug.diagnose_msgs) if tmpcount > 0: - print _("One or more plugins have detected a problem in your " \ - "configuration.") - print _("Please review the following messages:") - print + self.ui_log.info(_("One or more plugins have detected a problem in your " + "configuration.")) + self.ui_log.info(_("Please review the following messages:")) + self.ui_log.info("") - fp = open(rptdir + "/diagnose.txt", "w") - for plugname, plug in GlobalVars.loadedplugins: + fp = self.get_temp_file() + for plugname, plug in self.loaded_plugins: for tmpcount2 in range(0, len(plug.diagnose_msgs)): if tmpcount2 == 0: - soslog.warning( textcolor("%s:" % plugname, "red") ) + soslog.warning("%s:" % plugname) soslog.warning(" * %s" % plug.diagnose_msgs[tmpcount2]) fp.write("%s: %s\n" % (plugname, plug.diagnose_msgs[tmpcount2])) - fp.close() + self.archive.add_file(fp.name, dest=os.path.join(self.rptdir, 'diagnose.txt')) - print - if not GlobalVars.__cmdLineOpts__.batch: + self.ui_log.info("") + if not self.opts.batch: try: while True: - yorno = raw_input( _("Are you sure you would like to " \ + yorno = raw_input( _("Are you sure you would like to " "continue (y/n) ? ") ) if yorno == _("y") or yorno == _("Y"): - print + self.ui_log.info("") break elif yorno == _("n") or yorno == _("N"): - doExit(0) + self._exit(0) del yorno except KeyboardInterrupt: - print - doExit(0) + self.ui_log.info("") + self._exit(0) - GlobalVars.policy.preWork() - - # Call the setup() method for each plugin - for plugname, plug in GlobalVars.loadedplugins: + def prework(self): try: - plug.setup() - except KeyboardInterrupt: - raise - except: - if GlobalVars.__raisePlugins__: + self.policy.preWork() + self._set_archive() + except Exception, e: + import traceback + traceback.print_exc(e) + self.ui_log.info(e) + self._exit(0) + + def setup(self): + for plugname, plug in self.loaded_plugins: + try: + plug.setArchive(self.archive) + plug.setup() + except KeyboardInterrupt: raise - else: - error_log = open(logdir + "/sosreport-plugin-errors.txt", "a") - etype, eval, etrace = sys.exc_info() - traceback.print_exception(etype, eval, etrace, limit=2, file=sys.stdout) - error_log.write(traceback.format_exc()) - error_log.close() - - print _(" Running plugins. Please wait ...") - print - plugruncount = 0 - for i in izip(GlobalVars.loadedplugins): - plugruncount += 1 - plugname, plug = i[0] - sys.stdout.write("\r Running %d/%d: %s... " % (plugruncount, - len(GlobalVars.loadedplugins), - plugname)) - sys.stdout.flush() - try: - plug.copyStuff() - except KeyboardInterrupt: - raise - except: - if GlobalVars.__raisePlugins__: + except: + if self.raise_plugins: + raise + else: + self._log_plugin_exception(plugname) + + def version(self): + """Fetch version information from all plugins and store in the report + version file""" + + versions = [] + versions.append("sosreport: %s" % __version__) + for plugname, plug in self.loaded_plugins: + versions.append("%s: %s" % (plugname, plug.version)) + self.archive.add_string(content="\n".join(versions), dest='version.txt') + + + def copy_stuff(self): + plugruncount = 0 + for i in izip(self.loaded_plugins): + plugruncount += 1 + plugname, plug = i[0] + if not self.opts.silent: + sys.stdout.write("\r Running %d/%d: %s... " % (plugruncount, len(self.loaded_plugins), plugname)) + sys.stdout.flush() + try: + plug.copyStuff() + except KeyboardInterrupt: raise - else: - error_log = open(logdir + "/sosreport-plugin-errors.txt", "a") - etype, eval, etrace = sys.exc_info() - traceback.print_exception(etype, eval, etrace, limit=2, file=sys.stdout) - error_log.write(traceback.format_exc()) - error_log.close() - - print + except: + if self.raise_plugins: + raise + else: + self._log_plugin_exception(plugname) - if GlobalVars.__cmdLineOpts__.report: - for plugname, plug in GlobalVars.loadedplugins: + def report(self): + for plugname, plug in self.loaded_plugins: for oneFile in plug.copiedFiles: try: - xmlrep.add_file(oneFile["srcpath"], os.stat(oneFile["srcpath"])) + self.xml_report.add_file(oneFile["srcpath"], os.stat(oneFile["srcpath"])) except: pass - xmlrep.serialize_to_file(rptdir + "/sosreport.xml") + self.xml_report.serialize_to_file( + os.path.join(self.rptdir, "sosreport.xml")) - if GlobalVars.__cmdLineOpts__.analyze: - # Call the analyze method for each plugin - for plugname, plug in GlobalVars.loadedplugins: - try: - plug.analyze() - except: - # catch exceptions in analyze() and keep working - pass - if GlobalVars.__cmdLineOpts__.report: + def plain_report(self): + report = Report() + + for plugname, plug in self.loaded_plugins: + section = Section(name=plugname) + + for alert in plug.alerts: + section.add(Alert(alert)) + + if plug.customText: + section.add(Note(plug.customText)) + + for f in plug.copiedFiles: + section.add(CopiedFile(name=f["srcpath"], href=f["dstpath"])) + + for cmd in plug.executedCommands: + section.add(Command(name=cmd['exe'], return_code=0, href=cmd['file'])) + + for content, f in plug.copyStrings: + section.add(CreatedFile(name=f)) + + report.add(section) + + fd = self.get_temp_file() + fd.write(str(PlainTextReport(report))) + fd.flush() + self.archive.add_file(fd.name, dest=os.path.join('sos_reports', 'sos.txt')) + + + def html_report(self): # Generate the header for the html output file - rfd = open(rptdir + "/" + "sosreport.html", "w") + rfd = self.get_temp_file() 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"> @@ -772,9 +740,9 @@ No changes will be made to your system. # Make a pass to gather Alerts and a list of module names - allAlerts = deque() - plugNames = deque() - for plugname, plug in GlobalVars.loadedplugins: + allAlerts = deque() + plugNames = deque() + for plugname, plug in self.loaded_plugins: for alert in plug.alerts: allAlerts.append('<a href="#%s">%s</a>: %s' % (plugname, plugname, alert)) @@ -802,49 +770,179 @@ No changes will be made to your system. # Call the report method for each plugin - for plugname, plug in GlobalVars.loadedplugins: + for plugname, plug in self.loaded_plugins: try: html = plug.report() except: - if GlobalVars.__raisePlugins__: + if self.raise_plugins: raise else: rfd.write(html) rfd.write("</body></html>") - rfd.close() + rfd.flush() + + self.archive.add_file(rfd.name, dest=os.path.join('sos_reports', 'sos.html')) + + def postproc(self): + for plugname, plug in self.loaded_plugins: + try: + plug.postproc() + except: + if self.raise_plugins: + raise + + def final_work(self): + + # package up the results for the support organization + self.policy.packageResults(self.archive.name()) + + self._finish_logging() + + self.archive.close() - # Call the postproc method for each plugin - for plugname, plug in GlobalVars.loadedplugins: + final_filename = compress(self.archive, self.opts.compression_type) + + # automated submission will go here + if not self.opts.upload: + self.policy.displayResults(final_filename) + else: + self.policy.uploadResults(final_filename) + + self.tempfile_util.clean() + + return final_filename + + def ensure_plugins(self): + if not self.loaded_plugins: + self.soslog.error(_("no valid plugins were enabled")) + self._exit(1) + + def parse_options(self, opts): + """ Parse command line options """ + + self.parser = parser = OptionParserExtended(option_class=SosOption) + parser.add_option("-l", "--list-plugins", action="store_true", + dest="listPlugins", default=False, + help="list plugins and available plugin options") + parser.add_option("-n", "--skip-plugins", action="extend", + dest="noplugins", type="string", + help="skip these plugins", default = deque()) + parser.add_option("-e", "--enable-plugins", action="extend", + dest="enableplugins", type="string", + help="enable these plugins", default = deque()) + parser.add_option("-o", "--only-plugins", action="extend", + dest="onlyplugins", type="string", + help="enable these plugins only", default = deque()) + parser.add_option("-k", action="append", + dest="plugopts", type="string", + help="plugin options in plugname.option=value format (see -l)") + parser.add_option("-a", "--alloptions", action="store_true", + dest="usealloptions", default=False, + help="enable all options for loaded plugins") + parser.add_option("-u", "--upload", action="store", + dest="upload", default=False, + help="upload the report to an ftp server") + parser.add_option("--batch", action="store_true", + dest="batch", default=False, + help="do not ask any question (batch mode)") + parser.add_option("--no-colors", action="store_true", + dest="nocolors", default=False, + help="do not use terminal colors for text") + parser.add_option("-v", "--verbose", action="count", + dest="verbosity", + help="increase verbosity") + parser.add_option("", "--silent", action="store_true", + dest="silent", default=False, + help="Only display FATAL errors on stdout") + parser.add_option("--debug", action="count", + dest="debug", + help="enabling debugging through python debugger") + parser.add_option("--ticket-number", action="store", + dest="ticketNumber", + help="set ticket number") + parser.add_option("--name", action="store", + dest="customerName", + help="define customer name") + parser.add_option("--config-file", action="store", + dest="config_file", + help="specify alternate configuration file") + parser.add_option("--tmp-dir", action="store", + dest="tmp_dir", + help="specify alternate temporary directory", default=tempfile.gettempdir()) + parser.add_option("--diagnose", action="store_true", + dest="diagnose", + help="enable diagnostics", default=False) + parser.add_option("--analyze", action="store_true", + dest="analyze", + help="enable analyzations", default=False) + parser.add_option("--report", action="store_true", + dest="report", + help="Enable html/xml reporting", default=False) + parser.add_option("--profile", action="store_true", + dest="profiler", + help="turn on profiling", default=False) + parser.add_option("-z", "--compression-type", dest="compression_type", + help="compression technology to use [auto, zip, gzip, bzip2, xz] (default=auto)", + default="auto") + + return parser.parse_args(opts) + + def set_option(self, name, value=None): + """Allows setting of 'command line options' without passing in a + command line. Name should be the command line option name of the + option to set.""" + if self.parser.has_option(name): + option = self.parser.get_option(name) + option.process(name, value, self.opts, self.parser) + + def set_global_plugin_option(self, key, value): + self.global_plugin_options[key] = value; + + def execute(self): try: - plug.postproc() - except: - if GlobalVars.__raisePlugins__: - raise + self._setup_logging() + self.policy.setCommons(self.get_commons()) + self.print_header() + self.load_plugins() + self._set_tunables() + self._check_for_unknown_plugins() + self._set_plugin_options() - if GlobalVars.__cmdLineOpts__.build: - print - print _(" sosreport build tree is located at : %s" % (GlobalVars.dstroot,)) - print - return GlobalVars.dstroot + if self.opts.listPlugins: + self.list_plugins() + return + + self.ensure_plugins() + self.batch() + + if self.opts.diagnose: + self.diagnose() + + self.prework() + self.setup() + + self.ui_log.info(_(" Running plugins. Please wait ...")) + self.ui_log.info("") - # package up the results for the support organization - GlobalVars.policy.packageResults() + self.copy_stuff() - # delete gathered files - GlobalVars.policy.cleanDstroot() + self.ui_log.info("") - # let's encrypt the tar-ball - #if GlobalVars.__cmdLineOpts__.encrypt: - # policy.encryptResults() + if self.opts.report: + self.report() + self.html_report() + self.plain_report() - # automated submission will go here - if not GlobalVars.__cmdLineOpts__.upload: - GlobalVars.policy.displayResults() - else: - GlobalVars.policy.uploadResults() + self.postproc() + self.version() - # Close all log files and perform any cleanup - logging.shutdown() + return self.final_work() + except SystemExit: + return None +def main(args): + """The main entry point""" + sos = SoSReport(args) + sos.execute() diff --git a/sos/utilities.py b/sos/utilities.py new file mode 100644 index 00000000..e91586de --- /dev/null +++ b/sos/utilities.py @@ -0,0 +1,445 @@ +### 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 + +import os +import re +import sys +import string +import fnmatch +import inspect +from stat import * +from itertools import * +from subprocess import Popen, PIPE +import shlex +import logging +import zipfile +import tarfile +import hashlib +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO +import time + +def checksum(filename, chunk_size=128): + """Returns the checksum of the supplied filename. The file is read in + chunk_size blocks""" + name = get_hash_name() + digest = hashlib.new(name) + fd = open(filename, 'rb') + data = fd.read(chunk_size) + while data: + digest.update(data) + data = fd.read(chunk_size) + return digest.hexdigest() + +def get_hash_name(): + """Returns the algorithm used when computing a hash""" + import sos.policies + policy = sos.policies.load() + try: + name = policy.getPreferredHashAlgorithm() + hashlib.new(name) + return name + except: + return 'sha256' + +class DirTree(object): + """Builds an ascii representation of a directory structure""" + + def __init__(self, top_directory): + self.directory_count = 0 + self.file_count = 0 + self.buffer = [] + self.top_directory = top_directory + self._build_tree() + + def buf(self, s): + self.buffer.append(s) + + def printtree(self): + print self.as_string() + + def as_string(self): + return "\n".join(self.buffer) + + def _build_tree(self): + self.buf(os.path.abspath(self.top_directory)) + self.tree_i(self.top_directory, first=True) + + def _convert_bytes(self, n): + K, M, G, T = 1 << 10, 1 << 20, 1 << 30, 1 << 40 + if n >= T: + return '%.1fT' % (float(n) / T) + elif n >= G: + return '%.1fG' % (float(n) / G) + elif n >= M: + return '%.1fM' % (float(n) / M) + elif n >= K: + return '%.1fK' % (float(n) / K) + else: + return '%d' % n + + def _get_user(self, stats): + try: + import pwd + return pwd.getpwuid(stats.st_uid)[0] + except ImportError: + return str(stats.st_uid) + + def _get_group(self, stats): + try: + import grp + return grp.getgrgid(stats.st_gid)[0] + except ImportError: + return str(stats.st_uid) + + def _format(self, path): + """Conditionally adds detail to paths""" + stats = os.stat(path) + details = { + "filename": os.path.basename(path), + "user": self._get_user(stats), + "group": self._get_group(stats), + "filesize": self._convert_bytes(stats.st_size), + } + return ("[%(user)s %(group)s %(filesize)s] " % details, "%(filename)s" % details) + + def tree_i(self, dir_, padding='', first=False, fmt="%-30s %s%s%s"): + if not first: + details, filename = self._format(os.path.abspath(dir_)) + line = fmt % (details, padding[:-1], "+-- ", filename) + self.buf(line) + padding += ' ' + + count = 0 + files = os.listdir(dir_) + files.sort(key=string.lower) + for f in files: + count += 1 + path = os.path.join(dir_, f) + + if f.startswith("."): + pass + elif os.path.isfile(path): + self.file_count += 1 + details, filename = self._format(path) + line = fmt % (details, padding, "+-- ", filename) + self.buf(line) + elif os.path.islink(path): + self.buf(padding + + '+-- ' + + f + + ' -> ' + os.path.basename(os.path.realpath(path))) + if os.path.isdir(path): + self.directory_count += 1 + else: + self.file_count += 1 + elif os.path.isdir(path): + self.directory_count += 1 + if count == len(files): + self.tree_i(path, padding + ' ') + else: + self.tree_i(path, padding + '|') + + +class ImporterHelper(object): + """Provides a list of modules that can be imported in a package. + Importable modules are located along the module __path__ list and modules + are files that end in .py. This class will read from PKZip archives as well + for listing out jar and egg contents.""" + + def __init__(self, package): + """package is a package module + import my.package.module + helper = ImporterHelper(my.package.module)""" + self.package = package + + def _plugin_name(self, path): + "Returns the plugin module name given the path" + base = os.path.basename(path) + name, ext = os.path.splitext(base) + return name + + def _get_plugins_from_list(self, list_): + plugins = [self._plugin_name(plugin) + for plugin in list_ + if "__init__" not in plugin + and plugin.endswith(".py")] + plugins.sort() + return plugins + + def _find_plugins_in_dir(self, path): + if os.path.exists(path): + py_files = list(find("*.py", path)) + pnames = self._get_plugins_from_list(py_files) + if pnames: + return pnames + else: + return [] + + def _get_path_to_zip(self, path, tail_list=None): + if not tail_list: + tail_list = [''] + + if path.endswith(('.jar', '.zip', '.egg')): + return path, os.path.join(*tail_list) + + head, tail = os.path.split(path) + tail_list.insert(0, tail) + + if head == path: + raise Exception("not a zip file") + else: + return self._get_path_to_zip(head, tail_list) + + + def _find_plugins_in_zipfile(self, path): + try: + path_to_zip, tail = self._get_path_to_zip(path) + zf = zipfile.ZipFile(path_to_zip) + root_names = [name for name in zf.namelist() if tail in name] + candidates = self._get_plugins_from_list(root_names) + zf.close() + if candidates: + return candidates + else: + return [] + except (IOError, Exception): + return [] + + def get_modules(self): + "Returns the list of importable modules in the configured python package." + plugins = [] + for path in self.package.__path__: + if os.path.isdir(path) or path == '': + plugins.extend(self._find_plugins_in_dir(path)) + else: + plugins.extend(self._find_plugins_in_zipfile(path)) + + return plugins + + +def find(file_pattern, top_dir, max_depth=None, path_pattern=None): + """generator function to find files recursively. Usage: + + for filename in find("*.properties", "/var/log/foobar"): + print filename + """ + if max_depth: + base_depth = os.path.dirname(top_dir).count(os.path.sep) + max_depth += base_depth + + for path, dirlist, filelist in os.walk(top_dir): + if max_depth and path.count(os.path.sep) >= max_depth: + del dirlist[:] + + if path_pattern and not fnmatch.fnmatch(path, path_pattern): + continue + + for name in fnmatch.filter(filelist, file_pattern): + yield os.path.join(path, name) + + +def sosGetCommandOutput(command, timeout=300): + """Execute a command through the system shell. First checks to see if the + requested command is executable. Returns (returncode, stdout, 0)""" + # XXX: what is this doing this for? + cmdfile = command.strip("(").split()[0] + + possibles = [cmdfile] + [os.path.join(path, cmdfile) for path in os.environ.get("PATH", "").split(":")] + + if any(os.access(path, os.X_OK) for path in possibles): + p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, bufsize=-1) + stdout, stderr = p.communicate() + return (p.returncode, stdout.strip(), 0) + else: + return (127, "", 0) + + +def import_module(module_fqname, superclass=None): + module_name = module_fqname.rpartition(".")[-1] + module = __import__(module_fqname, globals(), locals(), [module_name]) + modules = [class_ for cname, class_ in + inspect.getmembers(module, inspect.isclass) + if class_.__module__ == module_fqname] + if superclass: + modules = [m for m in modules if issubclass(m, superclass)] + + return modules + +class Archive(object): + + _name = "unset" + + def prepend(self, src): + if src: + name = os.path.split(self._name)[-1] + return os.path.join(name, src.lstrip(os.sep)) + + +class TarFileArchive(Archive): + + def __init__(self, name): + self._name = name + self.tarfile = tarfile.open(self.name(), mode="w") + + def name(self): + return "%s.tar" % self._name + + def add_file(self, src, dest=None): + if dest: + dest = self.prepend(dest) + else: + dest = self.prepend(src) + + fp = open(src, 'rb') + content = fp.read() + fp.close() + + tar_info = tarfile.TarInfo(name=dest) + tar_info.size = len(content) + tar_info.mtime = os.stat(src).st_mtime + + self.tarfile.addfile(tar_info, StringIO(content)) + + def add_string(self, content, dest): + dest = self.prepend(dest) + tar_info = tarfile.TarInfo(name=dest) + tar_info.size = len(content) + tar_info.mtime = time.time() + self.tarfile.addfile(tar_info, StringIO(content)) + + def open_file(self, name): + try: + self.tarfile.close() + self.tarfile = tarfile.open(self.name(), mode="r") + name = self.prepend(name) + file_obj = self.tarfile.extractfile(name) + file_obj = StringIO(file_obj.read()) + return file_obj + finally: + self.tarfile.close() + self.tarfile = tarfile.open(self.name(), mode="a") + + def close(self): + self.tarfile.close() + + +class ZipFileArchive(Archive): + + def __init__(self, name): + self._name = name + try: + import zlib + self.compression = zipfile.ZIP_DEFLATED + except: + self.compression = zipfile.ZIP_STORED + + self.zipfile = zipfile.ZipFile(self.name(), mode="w", compression=self.compression) + + def name(self): + return "%s.zip" % self._name + + def add_file(self, src, dest=None): + if os.path.isdir(src): + # We may not need, this, but if we do I only want to do it + # one time + regex = re.compile(r"^" + src) + for path, dirnames, filenames in os.walk(src): + for filename in filenames: + filename = path + filename + if dest: + self.zipfile.write(filename, + self.prepend(re.sub(regex, dest, filename))) + else: + self.zipfile.write(filename, self.prepend(filename)) + else: + if dest: + self.zipfile.write(src, self.prepend(dest)) + else: + self.zipfile.write(src, self.prepend(src)) + + def add_string(self, content, dest): + info = zipfile.ZipInfo(self.prepend(dest), + date_time=time.localtime(time.time())) + info.compress_type = self.compression + info.external_attr = 0400 << 16L + self.zipfile.writestr(info, content) + + def open_file(self, name): + try: + self.zipfile.close() + self.zipfile = zipfile.ZipFile(self.name(), mode="r") + name = self.prepend(name) + file_obj = self.zipfile.open(name) + return file_obj + finally: + self.zipfile.close() + self.zipfile = zipfile.ZipFile(self.name(), mode="a") + + def close(self): + self.zipfile.close() + + +def compress(archive, method): + """Compress an archive object via method. ZIP archives are ignored. If + method is automatic then the following technologies are tried in order: xz, + bz2 and gzip""" + + if method == "zip": + return archive.name() + + methods = ['xz', 'bzip2', 'gzip'] + + if method in ('xz', 'bzip2', 'gzip'): + methods = [method] + + compressed = False + last_error = None + for cmd in ('xz', 'bzip2', 'gzip'): + if compressed: + break + try: + command = shlex.split("%s %s" % (cmd,archive.name())) + p = Popen(command, stdout=PIPE, stderr=PIPE, bufsize=-1) + stdout, stderr = p.communicate() + log = logging.getLogger('sos') + if stdout: + log.info(stdout) + if stderr: + log.error(stderr) + compressed = True + return archive.name() + "." + cmd.replace('ip','') + except Exception, e: + last_error = e + + if not compressed: + raise last_error + + +def shell_out(cmd): + """Uses subprocess.Popen to make a system call and returns stdout. + Does not handle exceptions.""" + p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) + return p.communicate()[0] +# vim:ts=4 sw=4 et @@ -15,13 +15,11 @@ """ sos entry point. """ -from sos.sosreport import sosreport, doExitCode +#from sos.sosreport import sosreport, doExitCode +from sos.sosreport import main import sys if __name__ == '__main__': - try: - sosreport(sys.argv[1:]) - except KeyboardInterrupt: - doExitCode() + main(sys.argv[1:]) # vim:ts=4 et sw=4 diff --git a/tests/archive_tests.py b/tests/archive_tests.py new file mode 100755 index 00000000..01c46395 --- /dev/null +++ b/tests/archive_tests.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python + +import unittest +import os +import tarfile +import zipfile + +from sos.utilities import TarFileArchive, ZipFileArchive + +class ZipFileArchiveTest(unittest.TestCase): + + def setUp(self): + self.zf = ZipFileArchive('test') + + def tearDown(self): + os.unlink('test.zip') + + def check_for_file(self, filename): + zf = zipfile.ZipFile('test.zip', 'r') + zf.getinfo(filename) + zf.close() + + def test_create(self): + self.zf.close() + + def test_add_file(self): + self.zf.add_file('tests/worker.py') + self.zf.close() + + self.check_for_file('test/tests/worker.py') + + def test_add_dir(self): + self.zf.add_file('tests/') + self.zf.close() + + self.check_for_file('test/tests/worker.py') + + def test_add_renamed(self): + self.zf.add_file('tests/worker.py', dest='tests/worker_renamed.py') + self.zf.close() + + self.check_for_file('test/tests/worker_renamed.py') + + def test_add_renamed_dir(self): + self.zf.add_file('tests/', 'tests_renamed/') + self.zf.close() + + self.check_for_file('test/tests_renamed/worker.py') + + def test_add_string(self): + self.zf.add_string('this is content', 'tests/string_test.txt') + self.zf.close() + + self.check_for_file('test/tests/string_test.txt') + + def test_get_file(self): + self.zf.add_string('this is my content', 'tests/string_test.txt') + + afp = self.zf.open_file('tests/string_test.txt') + self.assertEquals('this is my content', afp.read()) + + def test_overwrite_file(self): + self.zf.add_string('this is my content', 'tests/string_test.txt') + self.zf.add_string('this is my new content', 'tests/string_test.txt') + + afp = self.zf.open_file('tests/string_test.txt') + self.assertEquals('this is my new content', afp.read()) + +class TarFileArchiveTest(unittest.TestCase): + + def setUp(self): + self.tf = TarFileArchive('test') + + def tearDown(self): + os.unlink('test.tar') + + def check_for_file(self, filename): + rtf = tarfile.open('test.tar') + rtf.getmember(filename) + rtf.close() + + def test_create(self): + self.tf.close() + self.assertTrue(os.path.exists('test.tar')) + + def test_add_file(self): + self.tf.add_file('tests/worker.py') + self.tf.close() + + self.check_for_file('test/tests/worker.py') + + def test_add_dir(self): + self.tf.add_file('tests/') + self.tf.close() + + self.check_for_file('test/tests/worker.py') + + def test_add_renamed(self): + self.tf.add_file('tests/worker.py', dest='tests/worker_renamed.py') + self.tf.close() + + self.check_for_file('test/tests/worker_renamed.py') + + def test_add_renamed_dir(self): + self.tf.add_file('tests/', 'tests_renamed/') + self.tf.close() + + self.check_for_file('test/tests_renamed/worker.py') + + def test_add_string(self): + self.tf.add_string('this is content', 'tests/string_test.txt') + self.tf.close() + + self.check_for_file('test/tests/string_test.txt') + + def test_get_file(self): + self.tf.add_string('this is my content', 'tests/string_test.txt') + + afp = self.tf.open_file('tests/string_test.txt') + self.assertEquals('this is my content', afp.read()) + + def test_overwrite_file(self): + self.tf.add_string('this is my content', 'tests/string_test.txt') + self.tf.add_string('this is my new content', 'tests/string_test.txt') + + afp = self.tf.open_file('tests/string_test.txt') + self.assertEquals('this is my new content', afp.read()) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/report_tests.py b/tests/report_tests.py new file mode 100644 index 00000000..a08f0aec --- /dev/null +++ b/tests/report_tests.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +import unittest +import os + +try: + import json +except ImportError: + import simplejson as json + +from sos.reporting import Report, Section, Command, CopiedFile, CreatedFile, Alert +from sos.reporting import PlainTextReport + +class ReportTest(unittest.TestCase): + + def test_empty(self): + report = Report() + + expected = json.dumps({}) + + self.assertEquals(expected, str(report)) + + def test_nested_section(self): + report = Report() + section = Section(name="section") + report.add(section) + + expected = json.dumps({"section": {}}) + + self.assertEquals(expected, str(report)) + + def test_multiple_sections(self): + report = Report() + section = Section(name="section") + report.add(section) + + section2 = Section(name="section2") + report.add(section2) + + expected = json.dumps({"section": {}, + "section2": {},}) + + self.assertEquals(expected, str(report)) + + + def test_deeply_nested(self): + report = Report() + section = Section(name="section") + command = Command(name="a command", return_code=0, href="does/not/matter") + + section.add(command) + report.add(section) + + expected = json.dumps({"section": {"commands": [{"name": "a command", + "return_code": 0, + "href": "does/not/matter"}]}}) + + self.assertEquals(expected, str(report)) + + +class TestPlainReport(unittest.TestCase): + + def setUp(self): + self.report = Report() + self.section = Section(name="plugin") + self.div = PlainTextReport.DIVIDER + + def test_basic(self): + self.assertEquals("", str(PlainTextReport(self.report))) + + def test_one_section(self): + self.report.add(self.section) + + self.assertEquals("plugin\n" + self.div, str(PlainTextReport(self.report))) + + def test_two_sections(self): + section1 = Section(name="first") + section2 = Section(name="second") + self.report.add(section1, section2) + + self.assertEquals("first\n" + self.div + "\nsecond\n" + self.div, str(PlainTextReport(self.report))) + + def test_command(self): + cmd = Command(name="ls -al /foo/bar/baz", + return_code=0, + href="sos_commands/plugin/ls_-al_foo.bar.baz") + self.section.add(cmd) + self.report.add(self.section) + + self.assertEquals("plugin\n" + self.div + "\n- commands executed:\n * ls -al /foo/bar/baz", + str(PlainTextReport(self.report))) + + def test_copied_file(self): + cf = CopiedFile(name="/etc/hosts", href="etc/hosts") + self.section.add(cf) + self.report.add(self.section) + + self.assertEquals("plugin\n" + self.div + "\n- files copied:\n * /etc/hosts", + str(PlainTextReport(self.report))) + + def test_created_file(self): + crf = CreatedFile(name="sample.txt") + self.section.add(crf) + self.report.add(self.section) + + self.assertEquals("plugin\n" + self.div + "\n- files created:\n * sample.txt", + str(PlainTextReport(self.report))) + + def test_alert(self): + alrt = Alert("this is an alert") + self.section.add(alrt) + self.report.add(self.section) + + self.assertEquals("plugin\n" + self.div + "\n- alerts:\n ! this is an alert", + str(PlainTextReport(self.report))) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/worker_link b/tests/worker_link new file mode 120000 index 00000000..20be81a3 --- /dev/null +++ b/tests/worker_link @@ -0,0 +1 @@ +worker.py
\ No newline at end of file diff --git a/tools/osdetect.py b/tools/osdetect.py new file mode 100644 index 00000000..fea78cc5 --- /dev/null +++ b/tools/osdetect.py @@ -0,0 +1,87 @@ +''' +Created on Aug 2, 2011 +@author: Keith Robertson +''' + +import pprint +import os +import re + +class OSTypes: + """ + Utility class with enumerations for the various OSes to facilitate + OS detection. + """ + JAVA_OS_NAME_KEY='os.name' + + OS_TYPE_LINUX='Linux' + OS_TYPE_LINUX_PAT=re.compile('^%s$' % OS_TYPE_LINUX, re.I) + OS_TYPE_WIN='Windows' + OS_TYPE_WIN_PAT=re.compile('^%s$' % OS_TYPE_WIN, re.I) + OS_TYPE_AIX='AIX' + OS_TYPE_AIX_PAT=re.compile('^%s$' % OS_TYPE_AIX, re.I) + OS_TYPE_MAC='Mac OS' + OS_TYPE_MAC_PAT=re.compile('^%s$' % OS_TYPE_MAC, re.I) + OS_TYPE_390='OS/390' + OS_TYPE_390_PAT=re.compile('^%s$' % OS_TYPE_390, re.I) + OS_TYPE_HPUX='HP-UX' + OS_TYPE_HPUX_PAT=re.compile('^%s$' % OS_TYPE_HPUX, re.I) + +def printProps(): + try: + from java.lang import System + from java.util import Set + from java.util import Iterator + + set = System.getProperties().entrySet() + it = set.iterator() + while it.hasNext(): + me = it.next(); + print "Key (%s) Value(%s)" % (me.getKey(), me.getValue()) + except Exception, e: + print "ERROR: unable to print Java properties %s" % e + + + +def java_detect_os(): + """ + Try to load Java packages. If successful then we know we are running + in JYthon. Use the JRE to determine what type of OS and return the proper + policy. + """ + try: + from java.lang import System + + ostype = System.getProperty(OSTypes.JAVA_OS_NAME_KEY) + if ostype: + if OSTypes.OS_TYPE_LINUX_PAT.match(ostype): + print "Matched %s" % OSTypes.OS_TYPE_LINUX + # Lots of checks here to determine linux version. + #return proper policy here + elif OSTypes.OS_TYPE_WIN_PAT.match(ostype): + print "Matched %s" % OSTypes.OS_TYPE_WIN + elif OS_TYPE_AIX_PAT.match(ostype): + print "Matched %s" % OSTypes.OS_TYPE_AIX + elif OS_TYPE_MAC_PAT.match(ostype): + print "Matched %s" % OSTypes.OS_TYPE_MAC + elif OS_TYPE_390_PAT.match(ostype): + print "Matched %s" % OSTypes.OS_TYPE_390 + elif OS_TYPE_HPUX_PAT.match(ostype): + print "Matched %s" % OSTypes.OS_TYPE_HPUX + else: + raise Exception("Unsupported OS type of %s." % ostype) + else: + raise Exception("Unable to get %s from JRE's system properties." % OSTypes.JAVA_OS_NAME_KEY) + except Exception, e: + print "WARN: unable to print Java properties %s" % e + +def native_detect_os(): + print "here" + + + +if __name__ == '__main__': + #printProps() + detectOS() + + pass
\ No newline at end of file |