[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[PATCH 1/2] Add logpicker tool into utils



---
 utils/log_picker/__init__.py                |  139 +++++++++++++++
 utils/log_picker/archiving.py               |   99 +++++++++++
 utils/log_picker/argparser.py               |  141 +++++++++++++++
 utils/log_picker/logmining.py               |  251 +++++++++++++++++++++++++++
 utils/log_picker/sending/__init__.py        |   28 +++
 utils/log_picker/sending/bugzillasender.py  |   59 +++++++
 utils/log_picker/sending/emailsender.py     |   69 ++++++++
 utils/log_picker/sending/senderbaseclass.py |   24 +++
 utils/log_picker/sending/stratasender.py    |   55 ++++++
 utils/logpicker                             |  125 +++++++++++++
 10 files changed, 990 insertions(+), 0 deletions(-)
 create mode 100644 utils/log_picker/__init__.py
 create mode 100644 utils/log_picker/archiving.py
 create mode 100644 utils/log_picker/argparser.py
 create mode 100644 utils/log_picker/logmining.py
 create mode 100644 utils/log_picker/sending/__init__.py
 create mode 100644 utils/log_picker/sending/bugzillasender.py
 create mode 100644 utils/log_picker/sending/emailsender.py
 create mode 100644 utils/log_picker/sending/senderbaseclass.py
 create mode 100644 utils/log_picker/sending/stratasender.py
 create mode 100755 utils/logpicker

diff --git a/utils/log_picker/__init__.py b/utils/log_picker/__init__.py
new file mode 100644
index 0000000..70498d1
--- /dev/null
+++ b/utils/log_picker/__init__.py
@@ -0,0 +1,139 @@
+#!/usr/bin/python
+
+import os
+import sys
+import tempfile
+
+import archiving
+from archiving import ArchivationError
+from archiving import NoFilesArchivationError
+import sending
+from sending import SenderError
+import logmining
+from logmining import LogMinerError
+
+
+class LogPickerError(Exception):
+    pass
+
+
+class LogPicker(object):
+
+    def __init__(self, archive_obj=None, sender_obj=None, miners=[], 
+                    use_one_file=False):
+        self.sender_obj = sender_obj
+        self.archive_obj = archive_obj
+        self.miners = miners
+        
+        self.archive = None
+        self.tmpdir = None
+        self.files = []
+        self.filename = self._get_tmp_file("completelog") if use_one_file else None
+
+       
+    def _errprint(self, msg):
+        """Print message on stderr."""
+        sys.stderr.write('%s\n' % msg)
+
+    
+    def _get_tmp_file(self, name, suffix="", register=True):
+        """Create temp file."""
+        if not self.tmpdir:
+            self.tmpdir = tempfile.mkdtemp(prefix="z-logs-", dir="/tmp")
+        
+        fd, filename = tempfile.mkstemp(suffix=suffix, 
+                                prefix="lp-%s-" % name, dir=self.tmpdir)
+        if register:
+            self.files.append(filename)
+        return filename
+     
+    
+    def create_archive(self, name="logs"):
+        """Create archive (one file) containing multiple log files."""
+        self.archive = self._get_tmp_file(name, 
+                            suffix=self.archive_obj.file_ext, register=False)
+        try:
+            self.archive_obj.create_archive(self.archive, self.files)
+        except (ArchivationError):
+            os.remove(self.archive)
+            raise
+
+   
+    def send(self):
+        """Send log/archive with logs via sender object."""
+        
+        if not self.archive and len(self.files) > 1:
+            raise LogPickerError('More than one file to send. ' + \
+                    'You have to create archive. Use create_archive() method.')
+               
+        file = self.files[0]
+        contenttype="text/plain"
+        if self.archive:
+            file = self.archive
+            contenttype = self.archive_obj.mimetype
+        
+        self.sender_obj.sendfile(file, contenttype)
+
+    
+    def getlogs(self):
+        """Collect logs generated by miners passed to the constructor."""
+        
+        # self.filename != None means that we should put all logs into one file.
+        # self.filename == None means that every log should have its own file.
+        if self.filename:
+            f = open(self.filename, 'w')
+        
+        for miner in self.miners:
+            if not self.filename:
+                tmpfilename = self._get_tmp_file(miner.get_filename())
+                f = open(tmpfilename, 'w')
+            
+            desc = "%s\n\n" % (miner.get_description())
+            f.write(desc)
+            try:
+                miner(f).getlog()
+            except (LogMinerError) as e:
+                self._errprint("Warning: %s - %s" % (miner._name, e))
+                f.write("\n%s\n\n\n" % e)
+            
+            if not self.filename:
+                f.close()
+                # XXX Cut anaconda dump into pieces.
+                if miner == logmining.AnacondaLogMiner:
+                    self._cut_to_pieces(tmpfilename)
+        
+        if self.filename:
+            f.close()
+    
+           
+    def _cut_to_pieces(self, filename):
+        """Create multiple log files from Anaconda dump.
+        Attention: Anaconda dump file will be used and overwritten!
+        @filename file with Anaconda dump"""
+        actual_file = os.path.basename(filename)
+        files = {actual_file: []}
+        empty_lines = 0
+        
+        # Split file into memmory
+        for line in open(filename):
+            striped = line.strip()
+            
+            if not striped:
+                empty_lines += 1
+            elif empty_lines > 1 and striped.startswith('/') \
+                            and striped.endswith(':') and len(line) > 2:
+                actual_file = striped[:-1].rsplit('/', 1)[-1].replace('.', '-')
+                files[actual_file] = []
+                empty_lines = 0
+            
+            files[actual_file].append(line)
+        
+        # Overwrite original file
+        actual_file = os.path.basename(filename)
+        open(filename, 'w').writelines(files[actual_file])
+        del files[actual_file]
+        
+        # Write other individual files
+        for file in files:
+            open(self._get_tmp_file(file), 'w').writelines(files[file])
+        
diff --git a/utils/log_picker/archiving.py b/utils/log_picker/archiving.py
new file mode 100644
index 0000000..e7d7fa3
--- /dev/null
+++ b/utils/log_picker/archiving.py
@@ -0,0 +1,99 @@
+import os
+import tempfile
+import tarfile
+import bz2
+
+
+class ArchivationError(Exception):
+    pass
+
+class NoFilesArchivationError(ArchivationError):
+    pass
+
+class ArchiveBaseClass(object):
+    """Base class for archive classes."""
+
+    _compression = False
+    _ext = ".ext"
+    _mimetype = ""
+    
+    def __init__(self, *args, **kwargs):
+        self._tar_ext = ".tar"
+        pass
+    
+    @property
+    def support_compression(self):
+        """Return True if compression is supported/used."""
+        return self._compression
+        
+    @property
+    def file_ext(self):
+        """Return extension for output file."""
+        return self._ext
+       
+    @property 
+    def mimetype(self):
+        """Return archive mime type."""
+        return self._mimetype
+    
+    def _create_tmp_tar(self, filelist):
+        _, tmpfile = tempfile.mkstemp(suffix=self._tar_ext)
+        tar = tarfile.open(tmpfile, "w")
+        for name in filelist:
+            arcname = name.rsplit('/', 1)[-1]
+            tar.add(name, arcname=arcname)
+        tar.close()
+        return tmpfile
+       
+    def create_archive(self, outfilename, filelist):
+        raise NotImplementedError()
+
+
+class Bzip2Archive(ArchiveBaseClass):
+    """Class for bzip2 compression."""
+    
+    _compression = True
+    _ext = ".bz2"
+    _mimetype = "application/x-bzip2"
+    
+    def __init__(self, usetar=True, *args, **kwargs):
+        ArchiveBaseClass.__init__(self, args, kwargs)
+        self.usetar = usetar
+
+    @property
+    def file_ext(self):
+        """Return extension for output file."""
+        if self.usetar:
+            return "%s%s" % (self._tar_ext, self._ext)
+        return self._ext
+
+    def create_archive(self, outfilename, filelist):
+        """Create compressed archive containing files listed in filelist."""
+        if not filelist:
+            raise NoFilesArchivationError("No files to archive.")
+        
+        size = 0
+        for file in filelist:
+            size += os.path.getsize(file)
+        if size <= 0:
+            raise NoFilesArchivationError("No files to archive.")
+            
+        if not self.usetar and len(filelist) > 1:
+            raise ArchivationError("Bzip2 cannot archive multiple files without tar.")
+        
+        if self.usetar:
+            f_in_path = self._create_tmp_tar(filelist)
+        else:
+            f_in_path = filelist[0]
+         
+        f_in = open(f_in_path, 'rb')
+        f_out = bz2.BZ2File(outfilename, 'w')
+        f_out.writelines(f_in)
+        f_out.close()
+        f_in.close()
+        
+        if self.usetar:
+            os.remove(f_in_path)
+        
+        return outfilename
+
diff --git a/utils/log_picker/argparser.py b/utils/log_picker/argparser.py
new file mode 100644
index 0000000..71170d5
--- /dev/null
+++ b/utils/log_picker/argparser.py
@@ -0,0 +1,141 @@
+import optparse
+import sending
+
+
+class ArgError(Exception):
+    pass
+
+
+class _OptionParserWithRaise(optparse.OptionParser):
+    def error(self, msg):
+        raise ArgError(msg)
+
+
+class ArgParser(object):
+    
+    def __init__(self):
+        self.options = None
+        self.parser = None
+    
+    
+    def _generate_bz_it_group(self):
+        if sending.RHBZ in sending.NOT_AVAILABLE and sending.STRATA in sending.NOT_AVAILABLE:
+            return None
+        
+        if sending.RHBZ in sending.NOT_AVAILABLE:
+            group_title = "Send to Red Hat Ticketing system options"
+            bz_group = optparse.OptionGroup(self.parser, group_title)
+            bz_group.add_option("-r", "--rhel", 
+                    action="store_true",
+                    dest="strata", 
+                    help="Send report to Red Hat Ticketing system.")
+            bug_id_title = "Case number in Red Hat Ticketing system."
+            login_title = "Red Hat Customer Portal username."
+            
+        elif sending.STRATA in sending.NOT_AVAILABLE:
+            group_title = "Send to Bugzilla options"
+            bz_group = optparse.OptionGroup(self.parser, group_title)
+            bz_group.add_option("-b", "--bugzilla", 
+                    action="store_true", 
+                    dest="bugzilla", 
+                    help="Send report to Bugzilla.")
+            bug_id_title = "Bug id in bugzilla."
+            login_title = "Bugzilla username."
+            
+        else:
+            group_title = "Send to Bugzilla (Fedora) or Red Hat Ticketing system options"
+            bz_group = optparse.OptionGroup(self.parser, group_title)
+            bz_group.add_option("-b", "--bugzilla", 
+                    action="store_true", 
+                    dest="bugzilla",
+                    help="Send report to Bugzilla.")
+            bz_group.add_option("-r", "--rhel", 
+                    action="store_true", 
+                    dest="strata", 
+                    help="Send report to Red Hat Ticketing system.")
+            bug_id_title = "Bug id in bugzilla/Case number in Red Hat Ticketing system."
+            login_title = "Bugzilla/Red Hat Cutomer Portal username."
+                                    
+        bz_group.add_option("-i", "--idbug", 
+                    dest="bug_id",
+                    help=bug_id_title, 
+                    metavar="ID")
+        bz_group.add_option("-l", "--login", 
+                    dest="login",
+                    help=login_title, 
+                    metavar="USERNAME")
+
+        return bz_group
+    
+    
+    def _generate_email_group(self):
+        if sending.EMAIL in sending.NOT_AVAILABLE:
+            return None
+        
+        email_group = optparse.OptionGroup(self.parser, "Send to Email options")
+        email_group.add_option("-e", "--email", action="store_true", dest="email",
+                          help="Send report to email address.")
+        email_group.add_option("-s", "--server", dest="smtp_addr",
+                          help="SMTP server address.", metavar="ADDRESS")
+        email_group.add_option("-f", "--from", dest="from_addr",
+                          help="Your email address.", metavar="EMAIL")
+        email_group.add_option("-t", "--to", dest="to_addr",
+                          help="Destination email address.", metavar="EMAIL")
+        return email_group
+    
+    
+    def _create_parser(self):
+        self.parser = _OptionParserWithRaise()
+        self.parser.add_option("-c", "--comment", dest="bug_comment", default=None,
+                          help="Report comment.", metavar="COMMENT")
+        
+        # Bugzilla and Red Hat Ticketing system options
+        group = self._generate_bz_it_group()
+        if group: self.parser.add_option_group(group)
+        
+        # Email options
+        group = self._generate_email_group()
+        if group: self.parser.add_option_group(group)
+
+
+    def _parse(self):
+        (self.options, _) = self.parser.parse_args()
+    
+      
+    def _validate(self):
+        cnt = 0
+        if self.options.ensure_value('email', None): cnt += 1
+        if self.options.ensure_value('bugzilla', None): cnt += 1
+        if self.options.ensure_value('strata', None): cnt += 1
+        
+        if not cnt:
+            raise ArgError("No place to send specified.")
+        elif cnt > 1:
+            raise ArgError("Options -b and -r and -e  are mutually exclusive.")
+        
+        missing = []
+        if self.options.ensure_value('email', None):
+            if not self.options.smtp_addr: missing.append('-s')
+            if not self.options.from_addr: missing.append('-f')
+            if not self.options.to_addr: missing.append('-t')
+        elif self.options.ensure_value('bugzilla', None):
+            if not self.options.bug_id: missing.append('-i')
+            if not self.options.login: missing.append('-l')
+        elif self.options.ensure_value('strata', None):
+            if not self.options.bug_id: missing.append('-i')
+            if not self.options.login: missing.append('-l')
+        
+        if missing:
+            msg = ""
+            for arg in missing:
+                msg += '\nArgument "%s" is missing!' % arg
+            raise ArgError(msg)
+    
+    
+    def parse(self):
+        """Parse and validate command line arguments."""
+        self._create_parser()
+        self._parse()
+        self._validate()
+        return self.options
+
diff --git a/utils/log_picker/logmining.py b/utils/log_picker/logmining.py
new file mode 100644
index 0000000..218ca46
--- /dev/null
+++ b/utils/log_picker/logmining.py
@@ -0,0 +1,251 @@
+import os
+import stat
+import shlex
+import time
+import subprocess
+
+
+class LogMinerError(Exception):
+    pass
+
+
+class LogMinerBaseClass(object):
+    """Base class for LogMiner classes. 
+    LogMiner object represents one file/command/function 
+    to get useful information (log)."""
+        
+    _name = "name"
+    _description = "Description"
+    _filename = "filename"
+    _prefer_separate_file = True
+    
+    def __init__(self, logfile, *args, **kwargs):
+        """@logfile open file object. This open file object will be used for 
+        output generated during getlog() call."""
+        self.logfile = logfile
+        self._used = False
+    
+    @classmethod
+    def get_filename(cls):
+        """Suggested log filename."""
+        return cls._filename
+        
+    @classmethod
+    def get_description(cls):
+        """Log description."""
+        return cls._description
+    
+    def _write_separator(self):
+        self.logfile.write('\n\n')
+       
+    def _write_files(self, files):
+        if not isinstance(files, list):
+            files = [files]
+
+        if self._used:
+            self._write_separator()
+        self._used = True
+        
+        for filename in files:
+            self.logfile.write('%s:\n' % filename)
+            try:
+                f = open(filename, 'r')
+            except (IOError) as e:
+                self.logfile.write("Exception while opening: %s\n" % e)
+                continue
+            
+            self.logfile.writelines(f)
+            f.close()
+    
+    def _run_command(self, command):
+        if self._used:
+            self._write_separator()
+        self._used = True
+        
+        if isinstance(command, basestring):
+            command = shlex.split(command)
+        
+        proc = subprocess.Popen(command, stdout=subprocess.PIPE, 
+                                stderr=subprocess.PIPE)
+        (out, err) = proc.communicate()
+        self.logfile.write('STDOUT:\n%s\n' % out)
+        self.logfile.write('STDERR:\n%s\n' % err)
+        self.logfile.write('RETURN CODE: %s\n' % proc.returncode)
+    
+    def getlog(self):
+        """Create log and write it to a file object 
+        recieved in the constructor."""
+        self._action()
+        self._write_separator()
+    
+    def _action(self):
+        raise NotImplementedError()
+
+
+
+class AnacondaLogMiner(LogMinerBaseClass):
+    """Class represents way to get Anaconda dump."""
+    
+    _name = "anaconda_log"
+    _description = "Log dumped from Anaconda."
+    _filename = "anaconda-dump"
+    _prefer_separate_file = True
+
+    def _action(self):
+        # Actual state of /tmp
+        old_state = set(os.listdir('/tmp'))
+        
+        # Tell Anaconda to dump itself
+        try:
+            anaconda_pid = open('/var/run/anaconda.pid').read().strip()
+        except (IOError):
+            raise LogMinerError("Anaconda pid file doesn't exists")
+        
+        proc = subprocess.Popen(shlex.split("kill -s USR2 %s" % anaconda_pid), 
+                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        proc.communicate()
+        if proc.returncode:
+            raise LogMinerError('Error while sending signal to Anaconda')
+        
+        time.sleep(5)
+        
+        # Check if new traceback file exists
+        new_state = set(os.listdir('/tmp'))
+        tbpfiles = list(new_state - old_state)
+        
+        if not len(tbpfiles):
+            raise LogMinerError('Error: No anaconda traceback file exist')
+            
+        for file in tbpfiles:
+            if file.startswith('anaconda-tb-'):
+                tbpfile_name = file
+                break
+        else:
+            raise LogMinerError('Error: No anaconda traceback file exist')
+        
+        # Copy anaconda traceback log
+        self._write_files('/tmp/%s' % tbpfile_name)
+
+
+
+class FileSystemLogMiner(LogMinerBaseClass):
+    """Class represents way to get image of filesystem structure."""
+
+    _name = "filesystem"
+    _description = "Image of disc structure."
+    _filename = "filesystem"
+    _prefer_separate_file = True
+
+    FSTREE_FORMAT = "%1s %5s%1s %s"
+    DADPOINT = 1                    # Number of Digits After the Decimal POINT
+
+    def _action(self):
+        self._get_tree_structure()
+
+    def _size_conversion(self, size):
+        """Converts bytes into KB, MB or GB"""
+        if size > 1073741824:  # Gigabytes
+            size = round(size / 1073741824.0, self.DADPOINT)
+            unit = "G"
+        elif size > 1048576:   # Megabytes
+            size = round(size / 1048576.0, self.DADPOINT)
+            unit = "M"
+        elif size > 1024:      # Kilobytes
+            size = round(size / 1024.0, self.DADPOINT)
+            unit = "K"
+        else:
+            size = size
+            unit = ""
+        return size, unit
+    
+    
+    def _get_tree_structure(self, human_readable=True):
+        """Creates filesystem structure image."""
+        white_list = ['/sys',]
+        
+        logfile = self.logfile
+        
+        for path, dirs, files in os.walk('/'):
+            line = "\n%s:" % (path)
+            logfile.write('%s\n' % line)
+            
+            # List dirs
+            for dir in dirs:
+                fullpath = os.path.join(path, dir)
+                size = os.path.getsize(fullpath)
+                unit = ""
+                if human_readable:
+                    size, unit = self._size_conversion(size)
+                line = self.FSTREE_FORMAT % ("d", size, unit, dir)
+                logfile.write('%s\n' % line)
+            
+            # Skip mounted directories
+            original_dirs = dirs[:]
+            for dir in original_dirs:
+                dirpath = os.path.join(path, dir)
+                if os.path.ismount(dirpath) and not dirpath in white_list:
+                    dirs.remove(dir)
+            
+            # List files
+            for filename in files:               
+                fullpath = os.path.join(path, filename)
+                if os.path.islink(fullpath):
+                    line = self.FSTREE_FORMAT % ("l", "0", "", filename)
+                    line += " -> %s" % os.path.realpath(fullpath)
+                    if not os.path.isfile(fullpath):
+                        # Broken symlink
+                        line += " (Broken)"
+                else:
+                    stat_res = os.stat(fullpath)[stat.ST_MODE]
+                    if stat.S_ISREG(stat_res):
+                        filetype = "-"
+                    elif stat.S_ISCHR(stat_res):
+                        filetype = "c"
+                    elif stat.S_ISBLK(stat_res):
+                        filetype = "b"
+                    elif stat.S_ISFIFO(stat_res):
+                        filetype = "p"
+                    elif stat.S_ISSOCK(stat_res):
+                        filetype = "s"
+                    else:
+                        filetype = "-"
+                    
+                    size = os.path.getsize(fullpath)
+                    unit = ""
+                    if human_readable:
+                        size, unit = self._size_conversion(size)
+                    line = self.FSTREE_FORMAT % (filetype, size, unit, filename)
+                logfile.write('%s\n' % line)
+
+
+
+class DmSetupLsLogMiner(LogMinerBaseClass):
+    """Class represents way to get 'dmsetup ls --tree' output."""
+
+    _name = "dmsetup ls"
+    _description = "Output from \"dmsetup ls --tree\"."
+    _filename = "dmsetup-ls"
+    _prefer_separate_file = True
+
+    def _action(self):
+        self._run_command("dmsetup ls --tree")
+
+
+class DmSetupInfoLogMiner(LogMinerBaseClass):
+    """Class represents way to get 'dmsetup info' output."""
+
+    _name = "dmsetup info"
+    _description = "Output from \"dmsetup info -c\"."
+    _filename = "dmsetup-info"
+    _prefer_separate_file = True
+
+    def _action(self):
+        self._run_command("dmsetup info -c")
+
+
+ALL_MINERS = [AnacondaLogMiner,
+              FileSystemLogMiner,
+              DmSetupLsLogMiner,
+              DmSetupInfoLogMiner,
+             ]
+
diff --git a/utils/log_picker/sending/__init__.py b/utils/log_picker/sending/__init__.py
new file mode 100644
index 0000000..8f7f22b
--- /dev/null
+++ b/utils/log_picker/sending/__init__.py
@@ -0,0 +1,28 @@
+from senderbaseclass import SenderError
+
+RHBZ = 0   # RedHat Bugzilla
+EMAIL = 1  # Email
+STRATA = 2 # Red Hat ticketing system
+
+NOT_AVAILABLE = []
+
+
+try:
+    from bugzillasender import RedHatBugzilla
+except (ImportError):
+    NOT_AVAILABLE.append(RHBZ)
+    pass
+
+try:
+    from emailsender import EmailSender
+except (ImportError):
+    NOT_AVAILABLE.append(EMAIL)
+    pass
+
+try:
+    from stratasender import StrataSender
+except (ImportError):
+    NOT_AVAILABLE.append(STRATA)
+    pass
+
+    
diff --git a/utils/log_picker/sending/bugzillasender.py b/utils/log_picker/sending/bugzillasender.py
new file mode 100644
index 0000000..08f89ec
--- /dev/null
+++ b/utils/log_picker/sending/bugzillasender.py
@@ -0,0 +1,59 @@
+import os
+from senderbaseclass import *
+from report.plugins.bugzilla import filer
+from report.plugins.bugzilla.filer import CommunicationError
+from report.plugins.bugzilla.filer import LoginError
+
+
+class BugzillaBaseClass(SenderBaseClass):
+
+    _bz_address = ""
+    _bz_xmlrpc = ""
+    _description = ""
+    
+    def __init__(self, *args, **kwargs):
+        SenderBaseClass.__init__(self, args, kwargs)
+        self.bzfiler = None
+        self.bug_id = None
+        self.comment = None
+    
+    def connect_and_login(self, username, password):
+        try:
+            self.bzfiler = filer.BugzillaFiler(self._bz_xmlrpc, self._bz_address,
+                                        filer.getVersion(), filer.getProduct())
+            self.bzfiler.login(username, password)
+        except (CommunicationError, LoginError) as e:
+            raise SenderError("%s. Bad username or password?" % e)
+    
+    def set_bug(self, bug_id):
+        self.bug_id = bug_id
+    
+    def set_comment(self, comment):
+        self.comment = comment
+    
+    def sendfile(self, file, contenttype):
+        description = self._get_description(self._description)
+
+        dict_args = {'isprivate': False,
+                     'filename': os.path.basename(file),
+                     'contenttype': contenttype}
+        
+        if self.comment:
+            dict_args['comment'] = self.comment
+
+        try:
+            bug = self.bzfiler.getbug(self.bug_id)      
+            bug.attachfile(file, description, **dict_args)
+        except (CommunicationError, ValueError) as e:
+            raise SenderError(e)
+
+
+class RedHatBugzilla(BugzillaBaseClass):
+
+    _bz_address = "http://bugzilla.redhat.com";
+    _bz_xmlrpc = "https://bugzilla.redhat.com/xmlrpc.cgi";
+    _description = "LogPicker"
+    
+    def __init__(self, *args, **kwargs):
+        BugzillaBaseClass.__init__(self, args, kwargs)
+
diff --git a/utils/log_picker/sending/emailsender.py b/utils/log_picker/sending/emailsender.py
new file mode 100644
index 0000000..549edc5
--- /dev/null
+++ b/utils/log_picker/sending/emailsender.py
@@ -0,0 +1,69 @@
+import os
+import smtplib
+import email
+import email.mime.multipart
+from senderbaseclass import *
+
+
+class EmailSender(SenderBaseClass):
+   
+    _description = "Email from LogPicker"
+    
+    def __init__(self, sendby, addresses, smtp_server, *args, **kwargs):
+        """Send file by email.
+        @sendby - Sender email address. (string)
+        @addresses - List of destination email addresses. (list)
+        @smtp_server - SMTP server address. (string)"""
+        
+        SenderBaseClass.__init__(self, args, kwargs)
+        self.smtp_server = smtp_server
+        self.sendby = sendby
+        self.addresses = addresses
+        self.comment = ""
+    
+    def set_comment(self, comment):
+        self.comment = comment
+    
+    def sendfile(self, file, contenttype):
+        # Create email message
+        msg = email.mime.multipart.MIMEMultipart()
+        msg['Subject'] = self._get_description(self._description)
+        msg['From'] = self.sendby
+        msg['To'] = ', '.join(self.addresses)
+        msg.preamble = 'This is a multi-part message in MIME format.'
+        
+        # Add message text
+        msgtext = email.mime.base.MIMEBase("text", "plain")
+        msgtext.set_payload(self.comment)
+        msg.attach(msgtext)
+        
+        # Add message attachment
+        cont_type = contenttype.split('/', 1)
+        if len(cont_type) == 1:
+            cont_type.append("")
+        elif not cont_type:
+            cont_type = ["application", "octet-stream"]
+        
+        attach_data = open(file, 'rb').read()
+        
+        msgattach = email.mime.base.MIMEBase(cont_type[0], cont_type[1])
+        msgattach.set_payload(attach_data)
+        email.Encoders.encode_base64(msgattach)
+        msgattach.add_header('Content-Disposition', 'attachment', 
+                filename=os.path.basename(file))
+        msg.attach(msgattach)
+        
+        # Send message
+        try:
+            s = smtplib.SMTP(self.smtp_server)
+        except(smtplib.socket.gaierror, smtplib.SMTPServerDisconnected):
+            raise SenderError("Email cannot be send. " +\
+                                "Error while connecting to smtp server.")
+        
+        try:
+            s.sendmail(self.sendby, self.addresses, msg.as_string())
+        except(smtplib.SMTPRecipientsRefused) as e:
+            raise SenderError("Email cannot be send. Wrong destination " +\
+                                "email address?\nErr message: %s" % e)
+        s.quit()
+
diff --git a/utils/log_picker/sending/senderbaseclass.py b/utils/log_picker/sending/senderbaseclass.py
new file mode 100644
index 0000000..1605859
--- /dev/null
+++ b/utils/log_picker/sending/senderbaseclass.py
@@ -0,0 +1,24 @@
+import datetime
+from socket import gethostname
+
+
+class SenderError(Exception):
+    pass
+
+
+class SenderBaseClass(object):
+    
+    def __init__(self, *args, **kwargs):
+        pass
+    
+    def sendfile(self, file, contenttype):
+        raise NotImplementedError()
+    
+    def _get_description(self, prefix=""):
+        try:
+            hostname = gethostname()
+        except:
+            hostname = ""
+        date_str = datetime.datetime.now().strftime("%Y-%m-%d")
+        description = "%s (%s) %s" % (prefix, hostname, date_str)
+        return description
diff --git a/utils/log_picker/sending/stratasender.py b/utils/log_picker/sending/stratasender.py
new file mode 100644
index 0000000..c872655
--- /dev/null
+++ b/utils/log_picker/sending/stratasender.py
@@ -0,0 +1,55 @@
+from senderbaseclass import *
+from report.plugins.strata import send_report_to_existing_case, strata_client_strerror
+import xml.dom.minidom
+
+
+class StrataSender(SenderBaseClass):
+    
+    _URL = "https://api.access.redhat.com/rs";
+    _CERT_DATA = "INSECURE"
+    
+    def __init__(self, *args, **kwargs):
+        SenderBaseClass.__init__(self, args, kwargs)
+        self.username = None
+        self.password = None
+        self.case_number = None
+    
+    def set_login(self, username, password):
+        self.username = username
+        self.password = password
+        
+    def set_case_number(self, case_num):
+        self.case_number = case_num
+    
+    def sendfile(self, file, contenttype):
+        response = send_report_to_existing_case(self._URL,
+                                                self._CERT_DATA,
+                                                self.username, 
+                                                self.password,
+                                                self.case_number, 
+                                                file)
+        
+        if not response:
+            raise SenderError("Sending log to the Red Hat Ticket System fail" +\
+                    " - %s" % strata_client_strerror())
+        
+        # Try parse response
+        try:
+            dom = xml.dom.minidom.parseString(response)
+            response_node = dom.getElementsByTagName("response")[0]
+            title = response_node.getElementsByTagName("title")[0].childNodes[0].data
+            body = response_node.getElementsByTagName("body")[0].childNodes[0].data
+        except Exception as e:
+            raise SenderError("Sending log to the Red Hat Ticket System fail.")
+        
+        if title == "File Attachment Failed":
+            if body.startswith("401 Unauthorized"):
+                raise SenderError("Bad login or password.")
+            elif body.startswith("Error : CASE_NOT_FOUND"):
+                raise SenderError("Selected case doesn't exist.")
+            else:
+                raise SenderError("Sending log to the " +\
+                                    "Red Hat Ticket System fail - %s" % body.strip())
+        #elif title == "File Attachment Succeeded":
+        #    return
+
diff --git a/utils/logpicker b/utils/logpicker
new file mode 100755
index 0000000..f1b383d
--- /dev/null
+++ b/utils/logpicker
@@ -0,0 +1,125 @@
+#!/usr/bin/python
+
+import sys
+import getpass
+import log_picker
+
+import log_picker.argparser as argparser
+from log_picker.argparser import ArgError
+import log_picker.archiving as archiving
+from log_picker.archiving import ArchivationError
+from log_picker.archiving import NoFilesArchivationError
+import log_picker.sending as sending
+from log_picker.sending import SenderError
+import log_picker.logmining as logmining
+from log_picker.logmining import LogMinerError
+
+
+class ApplicationScope(object):
+    """Application configuration class."""
+    
+    def __init__(self, parser_options):       
+        self.bug_comment = parser_options.ensure_value('bug_comment', None)
+                        
+        self.bug_id = parser_options.ensure_value('bug_id', None)
+        self.login = parser_options.ensure_value('login', None)
+        self.password = None
+        
+        self.smtp_server = parser_options.ensure_value('smtp_addr', None)
+        self.from_addr = parser_options.ensure_value('from_addr', None)
+        self.to_addr = [parser_options.to_addr] if parser_options.ensure_value('to_addr', None) else []
+        
+        # sender
+        if parser_options.ensure_value('email', None):
+            self.sender = sending.EMAIL
+        elif parser_options.ensure_value('strata', None):
+            self.sender = sending.STRATA
+        elif parser_options.ensure_value('bugzilla', None):
+            self.sender = sending.RHBZ
+        else:
+            self.sender = None
+        
+        # miners
+        self.miners = logmining.ALL_MINERS
+
+
+class Injector(object):
+    """Main factory class."""
+
+    @staticmethod
+    def injectMainHelper(scope):
+        logpicker = Injector.injectLogPicker(scope)
+        return MainHelper(logpicker)
+    
+    @staticmethod
+    def injectLogPicker(scope):
+        sender = Injector.injectSender(scope)
+        archivator = Injector.injectArchivator(scope)
+        return log_picker.LogPicker(archive_obj=archivator, sender_obj=sender, 
+                                    miners=scope.miners)
+    
+    @staticmethod
+    def injectSender(scope):
+        if scope.sender == sending.RHBZ:
+            sender = sending.RedHatBugzilla()
+            sender.set_bug(scope.bug_id)
+            sender.set_comment(scope.bug_comment)
+            sender.connect_and_login(scope.login, scope.password)
+            return sender
+        if scope.sender == sending.EMAIL:
+            sender = sending.EmailSender(scope.from_addr, scope.to_addr, 
+                                         scope.smtp_server)
+            sender.set_comment(scope.bug_comment)
+            return sender
+        if scope.sender == sending.STRATA:
+            sender = sending.StrataSender()
+            sender.set_login(scope.login, scope.password)
+            sender.set_case_number(scope.bug_id)
+            return sender
+            
+        raise Exception("Unknown sender type.")
+    
+    @staticmethod
+    def injectArchivator(scope):
+        return archiving.Bzip2Archive()
+    
+
+class MainHelper(object):
+    """Main application class."""
+    
+    def __init__(self, logpicker):
+        self.picker = logpicker
+    
+    def run(self):
+        self.picker.getlogs()
+        self.picker.create_archive()
+        self.picker.send()
+        print "Successfully completed!"
+
+
+
+if __name__ == "__main__":
+    
+    # Argument parsing
+    try:
+        options = argparser.ArgParser().parse()
+    except (ArgError) as e:
+        sys.stderr.write("Argument error: %s\n" % e)
+        sys.exit(1)
+          
+    # Application scope
+    scope = ApplicationScope(options)
+    if scope.sender == sending.RHBZ or scope.sender == sending.STRATA:
+        scope.password = getpass.getpass("Password: ")
+    
+    # Application
+    try:
+        app = Injector.injectMainHelper(scope)
+        app.run()
+    except (NoFilesArchivationError):
+        sys.stderr.write("Nothing to report. Select more log gathering options.\n")
+        sys.exit(0)
+    except (SenderError) as e:
+        sys.stderr.write("Error: %s\n" % e)
+        sys.exit(1)
+    
-- 
1.7.2.3


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]