From 44ada8238bbe962ffd35cf4bd8216a891c99ffe0 Mon Sep 17 00:00:00 2001 From: Jesse Jaggars Date: Fri, 2 Dec 2011 11:24:53 -0600 Subject: [PATCH] Addressing: https://issues.jboss.org/browse/AS7-2768 using commons-cli to handle argument parsing duties. Adding --help adding the ability to change the host and port of the the management api to collect from fixing a windows superuser error and http auth error in the eap sos plugin fixing NPE in standalone mode adding os x support and prompts for username and password in external mode --- build/build.xml | 4 + build/pom.xml | 6 +- .../org/apache/commons/cli/main/module.xml | 32 +++ .../modules/org/jboss/as/jdr/main/module.xml | 1 + jdr/jboss-as-jdr/pom.xml | 5 + .../org/jboss/as/jdr/CommandLineMain.java | 45 +++- .../org/jboss/as/jdr/JdrReportService.java | 39 +++- .../java/org/jboss/as/jdr/SosInterpreter.java | 23 +- .../src/main/resources/sos/plugins/eap6.py | 78 ++++--- .../main/resources/sos/policies/__init__.py | 188 +++++++++++++++- .../src/main/resources/sos/policies/osx.py | 13 ++ .../src/main/resources/sos/policies/redhat.py | 212 +++--------------- .../main/resources/sos/policies/windows.py | 65 +----- .../src/main/resources/sos/sosreport.py | 42 +--- .../src/main/resources/sos/utilities.py | 35 ++- pom.xml | 7 + 16 files changed, 474 insertions(+), 321 deletions(-) create mode 100644 build/src/main/resources/modules/org/apache/commons/cli/main/module.xml create mode 100644 jdr/jboss-as-sos/src/main/resources/sos/policies/osx.py diff --git a/build/build.xml b/build/build.xml index 540ccebdb19..927bf991755 100644 --- a/build/build.xml +++ b/build/build.xml @@ -441,6 +441,10 @@ + + + + diff --git a/build/pom.xml b/build/pom.xml index 8646e867d27..7ff33d04149 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -164,7 +164,6 @@ - antlr antlr @@ -215,6 +214,11 @@ commons-beanutils + + commons-cli + commons-cli + + commons-codec commons-codec diff --git a/build/src/main/resources/modules/org/apache/commons/cli/main/module.xml b/build/src/main/resources/modules/org/apache/commons/cli/main/module.xml new file mode 100644 index 00000000000..495e1a9f916 --- /dev/null +++ b/build/src/main/resources/modules/org/apache/commons/cli/main/module.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/build/src/main/resources/modules/org/jboss/as/jdr/main/module.xml b/build/src/main/resources/modules/org/jboss/as/jdr/main/module.xml index f58eb0eb592..314c0e9c8cd 100644 --- a/build/src/main/resources/modules/org/jboss/as/jdr/main/module.xml +++ b/build/src/main/resources/modules/org/jboss/as/jdr/main/module.xml @@ -31,6 +31,7 @@ + diff --git a/jdr/jboss-as-jdr/pom.xml b/jdr/jboss-as-jdr/pom.xml index b749414d575..3041154310d 100644 --- a/jdr/jboss-as-jdr/pom.xml +++ b/jdr/jboss-as-jdr/pom.xml @@ -39,6 +39,11 @@ JBoss Application Server: JDR + + commons-cli + commons-cli + 1.2 + org.jboss.msc jboss-msc diff --git a/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/CommandLineMain.java b/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/CommandLineMain.java index 1b98f6babb7..0f68a1f2362 100644 --- a/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/CommandLineMain.java +++ b/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/CommandLineMain.java @@ -22,15 +22,32 @@ package org.jboss.as.jdr; - import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.OperationFailedException; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.HelpFormatter; /** * Provides a main for collecting a JDR report from the command line. * * @author Mike M. Clark + * @author Jesse Jaggars */ public class CommandLineMain { + private static CommandLineParser parser = new GnuParser(); + private static Options options = new Options(); + private static HelpFormatter formatter = new HelpFormatter(); + private static final String usage = "jdr.{sh,bat} [options]"; + + static { + options.addOption("H", "help", false, "prints help and exits"); + options.addOption("h", "host", true, "hostname that the management api is bound to. (default: localhost)"); + options.addOption("p", "port", true, "port that the management api is bound to. (default: 9990)"); + } + /** * Creates a JBoss Diagnostic Reporter (JDR) Report. A JDR report response * is printed to System.out. @@ -38,11 +55,35 @@ public class CommandLineMain { * @param args ignored */ public static void main(String[] args) { + String port = "9990"; + String host = "localhost"; + + try { + CommandLine line = parser.parse(options, args); + + if (line.hasOption("help")) { + formatter.printHelp(usage, options); + return; + } + if (line.hasOption("host")) { + host = line.getOptionValue("host"); + } + + if (line.hasOption("port")) { + port = line.getOptionValue("port"); + } + } catch (Exception e) { + formatter.printHelp(usage, options); + return; + } + + System.out.println("Initializing JBoss Diagnostic Reporter..."); + JdrReportService reportService = new JdrReportService(); JdrReport response = null; try { - response = reportService.standaloneCollect(); + response = reportService.standaloneCollect(host, port); } catch (OperationFailedException e) { System.out.println("Failed to complete the JDR report: " + e.getMessage()); } diff --git a/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/JdrReportService.java b/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/JdrReportService.java index 3ddf68507fe..f50d5905bd6 100644 --- a/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/JdrReportService.java +++ b/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/JdrReportService.java @@ -39,10 +39,13 @@ import org.jboss.msc.value.InjectedValue; import org.jboss.threads.JBossThreadFactory; +import java.io.Console; import java.security.AccessController; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.net.URL; +import java.net.HttpURLConnection; /** * Service that provides a {@link JdrReportCollector}. @@ -75,9 +78,41 @@ public static ServiceController addService(final ServiceTarg /** * Collect a JDR report when run outside the Application Server. */ - public JdrReport standaloneCollect() throws OperationFailedException { + public JdrReport standaloneCollect(String host, String port) throws OperationFailedException { + Console cons = System.console(); + String username = null; + String password = null; + + if (host == null) { + host = "localhost"; + } + if (port == null) { + port = "9990"; + } + + // Let's go ahead and see if we need to auth before prompting the user + // for a username and password + boolean must_auth = false; + + try { + URL managementApi = new URL("http://" + host + ":" + port + "/management"); + HttpURLConnection conn = (HttpURLConnection) managementApi.openConnection(); + int code = conn.getResponseCode(); + if (code != 200) { + must_auth = true; + } + } + catch(Exception e) { + } + + if (must_auth) { + if (cons != null) { + username = cons.readLine("Management username: "); + password = String.valueOf(cons.readPassword("Management password: ")); + } + } SosInterpreter interpreter = new SosInterpreter(); - return interpreter.collect(); + return interpreter.collect(username, password, host, port); } /** diff --git a/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/SosInterpreter.java b/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/SosInterpreter.java index 11bac893a42..33a9301b72d 100644 --- a/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/SosInterpreter.java +++ b/jdr/jboss-as-jdr/src/main/java/org/jboss/as/jdr/SosInterpreter.java @@ -55,6 +55,20 @@ public SosInterpreter() { } public JdrReport collect() throws OperationFailedException { + return collect(null, null, null, null); + } + + /** + * Sets up a String global variable for sosreport + */ + private void setSosVariable(PythonInterpreter interpreter, String name, String value) { + if (value != null) { + interpreter.set(name, value); + interpreter.exec("sos." + name + " = " + name); + } + } + + public JdrReport collect(String username, String password, String host, String port) throws OperationFailedException { ROOT_LOGGER.startingCollection(); Date startTime = new Date(); @@ -76,19 +90,24 @@ public JdrReport collect() throws OperationFailedException { ROOT_LOGGER.debug("locationDir = " + locationDir); ROOT_LOGGER.debug("homeDir = " + SosInterpreter.cleanPath(homeDir)); - PyObject report = null; + PyObject report = new PyObject(); try { interpreter.exec("sys.path.append(\"" + pyLocation + "\")"); + interpreter.exec("import sos"); // If we have a controller client, use it to // get runtime information. if (controllerClient != null) { - interpreter.exec("import sos"); interpreter.set("controller_client_proxy", new ModelControllerClientProxy(controllerClient)); interpreter.exec("sos.controllerClient = controller_client_proxy"); } + setSosVariable(interpreter, "as7_user", username); + setSosVariable(interpreter, "as7_pass", password); + setSosVariable(interpreter, "as7_host", host); + setSosVariable(interpreter, "as7_port", port); + interpreter.exec("from sos.sosreport import main"); interpreter.exec("args = shlex.split('-k eap6.home=\"" + homeDir + "\" --tmp-dir=\"" + locationDir + "\" -o eap6 --batch --report --compression-type=zip --silent')"); interpreter.exec("reportLocation = main(args)"); diff --git a/jdr/jboss-as-sos/src/main/resources/sos/plugins/eap6.py b/jdr/jboss-as-sos/src/main/resources/sos/plugins/eap6.py index 87acd437667..b137110a8f6 100644 --- a/jdr/jboss-as-sos/src/main/resources/sos/plugins/eap6.py +++ b/jdr/jboss-as-sos/src/main/resources/sos/plugins/eap6.py @@ -1,11 +1,6 @@ import os import re import zipfile -import platform -import fnmatch -import shlex -import subprocess -import string import urllib2 try: @@ -14,14 +9,17 @@ import simplejson as json from sos.plugins import Plugin, IndependentPlugin -from sos.utilities import DirTree, find, md5sum +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 - self.parameters = parameters + if parameters: + self.parameters = parameters + else: + self.parameters = {} def url_parts(self): """Generator function to split a url into (key, value) tuples. The url @@ -48,7 +46,8 @@ class EAP6(Plugin, IndependentPlugin): ("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), - ("address", 'hostname:port of the management api for jboss', '', 'localhost:9990'), + ("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), @@ -95,7 +94,7 @@ def __getMd5(self, file): retVal = "?" * 32 try: - retVal = md5sum(file, self.__MD5_CHUNK_SIZE) + retVal = checksum(file, self.__MD5_CHUNK_SIZE) except IOError, ioe: self.__alert("ERROR: Unable to open %s for reading. Error: %s" % (file,ioe)) @@ -164,11 +163,17 @@ def query_java(self, request_obj): return sos.controllerClient.execute(request).toJSONString(True) def query_http(self, request_obj, postdata=None): - host_port = self.getOption('address') - username = self.getOption('user') - password = self.getOption('pass') + # in the case where we are being called from jdr.sh we will likely + # get these things via the sos object + import sos + + host = getattr(sos, 'as7_host', None) or self.getOption('host') + port = getattr(sos, 'as7_port', None) or self.getOption('port') + + username = self.getOption('user') or getattr(sos, 'as7_user', None) + password = self.getOption('pass') or getattr(sos, 'as7_pass', None) - uri = "http://" + host_port + "/management" + request_obj.resource + "?" + uri = "http://" + host + ":" + port + "/management" json_data = {'operation': request_obj.operation, 'address': []} @@ -186,16 +191,13 @@ def query_http(self, request_obj, postdata=None): opener = urllib2.build_opener() if username and password: - params = {"realm": "PropertiesMgmtSecurityRealm", - "uri": uri, - "user": username, - "passwd": password} - - digest_auth_handler = urllib2.HTTPDigestAuthHandler() - digest_auth_handler.add_password(**params) - - basic_auth_handler = urllib2.HTTPBasicAuthHandler() - basic_auth_handler.add_password(**params) + 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) @@ -204,9 +206,11 @@ def query_http(self, request_obj, postdata=None): try: resp = opener.open(req) - resp.read() + return resp.read() except Exception, e: - self.addAlert("Could not query url: %s; error: %s" % (uri, e)) + err_msg = "Could not query url: %s; error: %s" % (uri, e) + self.addAlert(err_msg) + return err_msg def get_online_data(self): """ @@ -214,10 +218,13 @@ def get_online_data(self): information from a running system. """ for caller, outfile in [ - (Request(resource="/core-service/platform-mbean/type/threading", - operation="dump-all-threads", - parameters={"locked-synchronizers": "true", "locked-monitors": "true"}), "threaddump.json"), + # waiting for the fix in AS7 +# (Request(resource="/core-service/platform-mbean/type/threading", +# operation="dump-all-threads", +# parameters={"locked-synchronizers": "true", "locked-monitors": "true"}), "threaddump.json"), (Request(resource="/", parameters={"recursive": "true"}), "configuration.json"), + (Request(resource="/core-service/service-container", operation='dump-services'), "dump-services.json"), + (Request(resource="/subsystem/modcluster", operation='read-proxies-configuration'), "cluster-proxies-configuration.json"), ]: self.addStringAsFile(self.query(caller), filename=outfile) @@ -237,6 +244,7 @@ def __getFiles(self, configDirAry): 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 @@ -280,22 +288,24 @@ def postproc(self): Obfuscate passwords. """ + password_xml_regex = re.compile(r'.*', re.IGNORECASE) + for dir_ in self.__jbossServerConfigDirs: path = os.path.join(self.__jbossHome, dir_) - self.doRegexSub(os.path.join(path,"configuration","login-config.xml"), - re.compile(r'"password".*>.*', re.IGNORECASE), - r'"password">********') + self.doRegexSub(os.path.join(path,"configuration","*.xml"), + password_xml_regex, + r'********') - tmp = os.path.join(path,"conf", "props") + 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, "deploy") + tmp = os.path.join(path, "deployments") for dsFile in find("*-ds.xml", tmp): self.doRegexSub(dsFile, - re.compile(r".*", re.IGNORECASE), + password_xml_regex, r"********") diff --git a/jdr/jboss-as-sos/src/main/resources/sos/policies/__init__.py b/jdr/jboss-as-sos/src/main/resources/sos/policies/__init__.py index 2d3400f8880..ed554ee55c2 100644 --- a/jdr/jboss-as-sos/src/main/resources/sos/policies/__init__.py +++ b/jdr/jboss-as-sos/src/main/resources/sos/policies/__init__.py @@ -1,6 +1,11 @@ import os import platform -from sos.utilities import ImporterHelper, import_module +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 @@ -9,11 +14,15 @@ def import_policy(name): except ImportError: return None -def load(): +def load(cache={}): + if 'policy' in cache: + return cache.get('policy') + helper = ImporterHelper(os.path.join('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.") @@ -47,6 +56,29 @@ def allPkgs(self): 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 @@ -66,13 +98,15 @@ def getArchiveName(self): This function should return the filename of the archive without the extension. """ - return "unset" + 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 False + return issubclass(plugin_class, IndependentPlugin) def preWork(self): """ @@ -80,12 +114,21 @@ def preWork(self): """ 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() @@ -93,3 +136,140 @@ def _parse_uname(self): 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/jdr/jboss-as-sos/src/main/resources/sos/policies/osx.py b/jdr/jboss-as-sos/src/main/resources/sos/policies/osx.py new file mode 100644 index 00000000000..60b7f6a98b2 --- /dev/null +++ b/jdr/jboss-as-sos/src/main/resources/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/jdr/jboss-as-sos/src/main/resources/sos/policies/redhat.py b/jdr/jboss-as-sos/src/main/resources/sos/policies/redhat.py index 42a3fd9f9df..cfd31ef2b19 100644 --- a/jdr/jboss-as-sos/src/main/resources/sos/policies/redhat.py +++ b/jdr/jboss-as-sos/src/main/resources/sos/policies/redhat.py @@ -26,17 +26,12 @@ import re import platform import time -from subprocess import Popen, PIPE, call from collections import deque -try: - from hashlib import md5 -except ImportError: - from md5 import md5 - 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: @@ -51,8 +46,10 @@ class RHELPackageManager(PackageManager): def _get_rpm_list(self): - pkg_list = subprocess.Popen(["rpm", "-qa", "--queryformat", "%{NAME}|%{VERSION}\\n"], - stdout=subprocess.PIPE).communicate()[0].splitlines() + pkg_list = shell_out(["rpm", + "-qa", + "--queryformat", + "%{NAME}|%{VERSION}\\n"]).splitlines() self._rpms = {} for pkg in pkg_list: name, version = pkg.split("|") @@ -89,23 +86,11 @@ def pkgNVRA(self, pkg): class RHELPolicy(Policy): def __init__(self): - self.report_file = "" - self.report_file_ext = "" - self.report_md5 = "" + super(RHELPolicy, self).__init__() self.reportName = "" self.ticketNumber = "" - self._parse_uname() self.package_manager = RHELPackageManager() - def _print(self, msg=None): - """A wrapper around print that only prints if we are not running in - silent mode""" - if not self.cInfo['cmdlineopts'].silent: - print msg - - def setCommons(self, commons): - self.cInfo = commons - def validatePlugin(self, plugin_class): "Checks that the plugin will execute given the environment" return issubclass(plugin_class, RedHatPlugin) or issubclass(plugin_class, IndependentPlugin) @@ -115,29 +100,35 @@ 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 is_root(self): - return (os.getuid() == 0) - 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 _system(self, cmd): - p = Popen(cmd, + 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) - stdout, stderr = p.communicate() - status = p.returncode - return stdout, stderr, status - - def runlevelByService(self, name): - ret = [] - out, err, sts = self._system("LC_ALL=C /sbin/chkconfig --list %s" % name) + out, err = p.communicate() if err: return ret for tabs in out.split()[1:]: @@ -201,7 +192,7 @@ def preWork(self): localname = self.rhnUsername() if len(localname) == 0: localname = self.hostName() - if not self.cInfo['cmdlineopts'].batch and not self.cInfo['cmdlineopts'].silent: + 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) @@ -216,158 +207,23 @@ def preWork(self): if len(self.reportName) == 0: self.reportName = localname - if self.cInfo['cmdlineopts'].customerName: - self.reportName = self.cInfo['cmdlineopts'].customerName + 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.cInfo['cmdlineopts'].ticketNumber: - self.ticketNumber = self.cInfo['cmdlineopts'].ticketNumber + 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...")) - self.report_file = archive_filename - # actually compress the archive if necessary - - def getArchiveName(self): - if len(self.ticketNumber): - self.reportName = self.reportName + "." + self.ticketNumber - else: - self.reportName = self.reportName - - return "sosreport-%s-%s" % (self.reportName, time.strftime("%Y%m%d%H%M%S")) - - def encryptResults(self): - # make sure a report exists - if not self.report_file: - return False - - self._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: - self._print(_("There was a problem encrypting your report.")) - sys.exit(1) - - def displayResults(self, final_filename=None): - self.report_file = final_filename - - # 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() - - # store md5 into file - fp = open(self.report_file + ".md5", "w") - fp.write(self.report_md5 + "\n") - fp.close() - - self._print() - self._print(_("Your sosreport has been generated and saved in:\n %s") % self.report_file) - self._print() - if len(self.report_md5): - self._print(_("The md5sum is: ") + self.report_md5) - self._print() - self._print(_("Please send this file to your support representative.")) - self._print() - - def uploadResults(self, final_filename): - - self.report_file = final_filename - - # make sure a report exists - if not self.report_file: - return False - - self._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: - 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(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 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 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/jdr/jboss-as-sos/src/main/resources/sos/policies/windows.py b/jdr/jboss-as-sos/src/main/resources/sos/policies/windows.py index ef078f9df1a..64e780bfacd 100644 --- a/jdr/jboss-as-sos/src/main/resources/sos/policies/windows.py +++ b/jdr/jboss-as-sos/src/main/resources/sos/policies/windows.py @@ -15,78 +15,27 @@ import time from sos.policies import PackageManager, Policy -from sos.plugins import IndependentPlugin -import subprocess - -try: - from hashlib import md5 -except ImportError: - from md5 import md5 +from sos.utilities import shell_out class WindowsPolicy(Policy): - def __init__(self): - self._parse_uname() - self.ticketNumber = None - self.reportName = self.hostname - self.package_manager = PackageManager() - - def setCommons(self, commons): - self.commons = commons - - def validatePlugin(self, plugin_class): - return issubclass(plugin_class, IndependentPlugin) + distro = "Microsoft Windows" @classmethod def check(class_): try: - p = subprocess.Popen("ver", shell=True, stdout=subprocess.PIPE) - ver_string = p.communicate()[0] - return "Windows" in ver_string + return "Windows" in shell_out("ver") except Exception, e: return False def is_root(self): - p = subprocess.Popen("whoami /groups", - shell=True, stdout=subprocess.PIPE) - stdout = p.communicate()[0] - if "S-1-16-12288" in stdout: + if "S-1-16-12288" in shell_out("whoami /groups"): return True else: - cmd = 'net localgroup administrators | find "%USERNAME"' - print cmd - return subprocess.call(cmd, shell=True) == 0 + 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 - - def pkgByName(self, name): - return None - - def preWork(self): - pass - - def packageResults(self, archive_filename): - self.report_file = archive_filename - - def getArchiveName(self): - if self.ticketNumber: - self.reportName += "." + self.ticketNumber - return "sosreport-%s-%s" % (self.reportName, time.strftime("%Y%m%d%H%M%S")) - - def displayResults(self, final_filename=None): - - if not final_filename: - return False - - fp = open(final_filename, "r") - md5sum = md5(fp.read()).hexdigest() - fp.close() - - fp = open(final_filename + ".md5", "w") - fp.write(md5sum + "\n") - fp.close() - - def uploadResults(self, final_filename=None): - pass diff --git a/jdr/jboss-as-sos/src/main/resources/sos/sosreport.py b/jdr/jboss-as-sos/src/main/resources/sos/sosreport.py index 1477d131ff7..a0286dd220d 100644 --- a/jdr/jboss-as-sos/src/main/resources/sos/sosreport.py +++ b/jdr/jboss-as-sos/src/main/resources/sos/sosreport.py @@ -47,18 +47,11 @@ import textwrap import tempfile -import subprocess -import shlex - 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): @@ -274,18 +267,6 @@ def serialize_to_file(self, fname): class SoSReport(object): - 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. - -This process may take a while to complete. -No changes will be made to your system. - -""" % {'distroa':__distro__, 'distrob':__distro__}) - def __init__(self, opts): self.loaded_plugins = deque() self.skipped_plugins = deque() @@ -426,7 +407,7 @@ def _setup_logging(self): self.soslog.addHandler(console) # ui log - self.ui_log = logging.getLogger('sos.ui') + 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() @@ -456,7 +437,7 @@ def _finish_logging(self): # 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 logger in [logging.getLogger(x) for x in ('sos', 'sosprofile', 'sos_ui')]: for h in logger.handlers: logger.removeHandler(h) @@ -521,12 +502,12 @@ def load_plugins(self): 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") + 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") + self._skip(plugin_class, _("requires root")) continue # plug-in is valid, let's decide whether run it or not @@ -537,7 +518,7 @@ def load_plugins(self): self._is_not_default(plugbase, plugin_class), self._is_not_specified(plugbase), )): - self._skip(plugin_class, "inactive") + self._skip(plugin_class, _("inactive")) continue self._load(plugin_class) @@ -667,11 +648,12 @@ def list_plugins(self): def batch(self): if self.opts.batch: - self.ui_log.info(self.msg) + self.ui_log.info(self.policy.get_msg()) else: - self.msg += _("Press ENTER to continue, or CTRL-C to quit.\n") + msg = self.policy.get_msg() + msg += _("Press ENTER to continue, or CTRL-C to quit.\n") try: - raw_input(self.msg) + raw_input(msg) except: self.ui_log.info("") self._exit() @@ -681,7 +663,7 @@ def _log_plugin_exception(self, plugin_name): def diagnose(self): tmpcount = 0 - for plugname, plug in GlobalVars.loadedplugins: + for plugname, plug in self.loaded_plugins: try: plug.diagnose() except: @@ -697,14 +679,14 @@ def diagnose(self): self.ui_log.info(_("Please review the following messages:")) self.ui_log.info("") - fp = open(os.path.join(rptdir, "diagnose.txt"), "w") + 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("%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')) self.ui_log.info("") if not self.opts.batch: diff --git a/jdr/jboss-as-sos/src/main/resources/sos/utilities.py b/jdr/jboss-as-sos/src/main/resources/sos/utilities.py index 464174d8fdc..6c433cfadeb 100644 --- a/jdr/jboss-as-sos/src/main/resources/sos/utilities.py +++ b/jdr/jboss-as-sos/src/main/resources/sos/utilities.py @@ -33,26 +33,35 @@ import logging import zipfile import tarfile +import hashlib try: from cStringIO import StringIO except ImportError: from StringIO import StringIO import time -try: - import hashlib as md5 -except ImportError: - import md5 - -def md5sum(filename, chunk_size=128): - """Returns the md5 sum of the supplied filename. The file is read in chunk_size blocks""" +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) - md5_obj = md5.md5() while data: - md5_obj.update(data) + digest.update(data) data = fd.read(chunk_size) - return md5_obj.hexdigest() + 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""" @@ -424,4 +433,10 @@ def compress(archive, method): 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 diff --git a/pom.xml b/pom.xml index 0b57fa9ced3..0178df5efba 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ 3.3.1 0.7.3 1.8.0 + 1.2 1.4 3.2.1 1.4 @@ -1127,6 +1128,12 @@ + + commons-cli + commons-cli + ${version.commons-cli} + + commons-codec commons-codec