#!/usr/bin/python
#  cas-admin
#  CAS Administration
#  Copyright (C) 2010 Adam Stokes
#  
#  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 3 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, see <http://www.gnu.org/licenses/>.

""" cas-admin - fingerprinting utility for cas
"""
import sys

try:
    from multiprocessing import Process, Queue, current_process, cpu_count, Lock
except ImportError:
    raise SystemExit('Unable to import multiprocessing module. This is shipped by' \
                     'default in Python 2.6 and above. If using Python 2.4/2.5 a' \
                     'backport is available at http://pypi.python.org/pypi/multiprocessing')
try:
    from caslib.db import *
    # Setup session for db interaction
except:
    raise SystemExit('Can not interface with database, please check configuration settings' \
                     'and try again')

# Must have requirements now loaded -- we can continue
import os
import ConfigParser
import optparse
import datetime
import paramiko
import tempfile

from caslib.core import casexecute, coreTimestamp, coreIsCorefile
from caslib.util import Utility, genprint
from caslib.rpmutils import extractDebug
from caslib import error_messages as _e

if sys.version_info[:2] < (2,6):
    import caslib.cas_shutil as shutil
else:
    import shutil

# Read in configuration
config = ConfigParser.ConfigParser()
config.read("/etc/cas.conf")
settings = {}
if config.has_section("settings"):
    for opt, val in config.items("settings"):
        settings[opt.upper()] = val

# read maintenance options
PURGELIMIT = config.get("maintenance","purgeLimit")
AUTOPURGE = config.getboolean("maintenance","autoPurge")

# advance options
BUFFERSIZE=None
if config.has_option("advanced", "buffersize"):
    BUFFERSIZE=config.get("advanced", "buffersize")

util = Utility()

# Gather number of cpu's
PROCESSES = cpu_count()
def queryWorker(input):
    """ worker function to process rpm debugs """
    for debug, dst in iter(input.get, 'STOP'):
        session = Session()
        debug_IN_DB = session.query(Debug).filter_by(debugPath=debug).first()
        session.close()
        if not debug_IN_DB:
            # add debug to database
            session = Session()
            debug_record = Debug(debug)
            session.add(debug_record)
            session.commit()
            if util.testMimetype(debug, "application/x-rpm") == True:
                genprint("(extracting) %-50s" % (os.path.basename(debug),))
                tmpkernel = extractDebug(debug, dst)
                # Sort through extracted debug for each type
                # e.g. hugemem, PAE, smp, largesmp
                for item in tmpkernel:
                    vmlinux = item.strip()
                    debugKernel = os.path.normpath(vmlinux)
                    timestamp = coreTimestamp(debugKernel, BUFFERSIZE)
                    # add id, debug, timestamp to database
                    timestamp_record = Timestamp(timestamp,
                                                 debugKernel,
                                                 debug_record.debugId)
                    session.add(timestamp_record)
                    # commit to database
                    session.commit()
                    # cleanup debug kernel since we store it in RPM
                    os.remove(debugKernel)
            if util.testMimetype(debug, "application/x-executable") == True:
                genprint("(reading) %s" % (os.path.basename(debug),))
                timestamp = coreTimestamp(debug, BUFFERSIZE)
                timestamp_record = Timestamp(timestamp,
                                             debug[1:],
                                             debug_record.debugId)
                session.add(timestamp_record)
                session.commit()
            session.close()

def queryLocalRpms():
    """ query debug kernels located on the filesystem
    
    Arguments:
    - `path`: filesystem location of debug kernels
    """
    # create Queues
    task_queue = Queue()
    cmd = "find -L %s -regextype %s " % (settings["KERNELS"],
                                         settings["REGEXTYPE"])
    concat_filter = ""
    if "RPMFILTER" in settings:
        rpmfilter = settings["RPMFILTER"].split(',')
        rpmfilter_len = len(rpmfilter)
        cmd = cmd + "\( -iregex %s" % (rpmfilter[0],)
        if rpmfilter_len > 1:
            concat_filter = ''.join([" -o -iregex %s " % (i,) for i in rpmfilter[1:]])
        cmd = cmd + concat_filter + " \) "
    # append any file/dir exclusions
    if "REGEXEXCLUDEDIR" in settings:
        for i in settings["REGEXEXCLUDEDIR"].split(','):
            cmd = cmd + " \( -not -path %s \) " % (i,)
    if "REGEXEXCLUDEFILE" in settings:
        for i in settings["REGEXEXCLUDEFILE"].split(','):
            cmd = cmd + " \( -not -name %s \) " % (i,)
    results, err = casexecute(cmd, True)
    dst = tempfile.mkdtemp(prefix='cas-')
    for rpm in results.split():
        task_queue.put((rpm, dst))

    for i in range(PROCESSES):
        Process(target=queryWorker, args=(task_queue,)).start()
    for i in range(PROCESSES):
        task_queue.put('STOP')
        
    return

def queryServers():
    """ query accessible servers
    """
    host = None
    port = 22
    hostname_count = 0
    ssh_obj = paramiko.SSHClient()
    ssh_obj.load_host_keys(os.path.expanduser("~/.ssh/known_hosts"))
    for server in ssh_obj.get_host_keys().keys():
        server = server.strip()
        # here we test for ports and apply accordingly
        if ':' in server:
            server, port = server.split(':')
            # ssh keys place [host] when defining servers with ports
            server = server[1:-1]
        try:
            ssh_obj.connect(server, port=port, username=settings["CASUSER"])
        except paramiko.AuthenticationException, e:
            return (e)
        stdin, stdout, stderr = ssh_obj.exec_command("/bin/uname -m")
        if stderr:
            genprint(stderr)
        # clean up arch string
        for i in stdout.readlines():
            arch = i.strip()
        server_record = Server(server, port, arch)
        session.add(server_record)
        hostname_count = hostname_count + 1
        genprint("Server database built with %d server(s) added." % \
                 (hostname_count,))
        session.commit()
        return

def purgeData(days):
    """ purge data from filesystem older then `days`
    
    Arguments:
    - `days`: amount of days to go back when purging data
    """
    todaysDate = datetime.date.today()
    # create date of timedelta
    cutOffDate = todaysDate - datetime.timedelta(days=days)
    # Start of purging data
    for root, dirs, files in os.walk(settings["WORKDIRECTORY"]):
        for d in dirs:
            dirpath = os.path.join(root,d)
            # pull out date from directory structure and trim it to
            # (year, month, day)
            searchDate = util.regexSearch('(\d{4})\.(\d+)\.(\d+)', dirpath)
            if searchDate:
                genprint('found %s' % (searchDate,))
                (year, month, day) = searchDate.split('.')
                # create our datetime object so we can do some arithmetic
                dirDate = datetime.date(int(year), int(month), int(day))
                if dirDate < cutOffDate:
                    genprint('Should purge old directories, %s' % (dirpath,))
                    rmtree(dirpath)
                    
if __name__=="__main__":
    parser = optparse.OptionParser(usage="cas-admin [opts] args")
    parser.add_option("--server", dest="queryServer",
                      help="Add remote CAS servers",
                      action="store_true", default=False)
    parser.add_option("-p","--purge", dest="purgeData",
                      help="Purge files default 90 days, customize with -d",
                      action="store_true", default=False)
    parser.add_option("-d","--days", dest="purgeDataDays",
                      help="Set how many days back to purge data")
    (opts, args) = parser.parse_args()
    purgeData = opts.purgeData
    purgeDataDays = opts.purgeDataDays
    queryServer = opts.queryServer

    """initialize CAS requirements
    """
    if os.getuid() is not 0:
        raise SystemExit(_e[100])

    genprint("Starting requirement processing and database alterations.")
    if not os.path.isdir(os.path.dirname(settings["DATABASE"])):
        os.makedirs(os.path.dirname(settings["DATABASE"]))

    # if autopurge is enabled lets clean up some stale data
    if AUTOPURGE:
        genprint('Autopurge enabled, purging stale data')
        purgeData(int(PURGELIMIT))

    if purgeData:
        ans = raw_input(_("You are about to purge data, is this what you " \
                            "really want to do? [Y/y/N/n]: "))
        if ans=='Y' or ans=='y':
            if not purgeDataDays:
                purgeDataDays = PURGELIMIT
                genprint("Beginning Purge going back %s day(s)" % \
                         (purgeDataDays,))
                purgeData(int(purgeDataDays))

    if queryServer:
        genprint("Generating accessible CAS server list")
        queryServers()

    # Got all the pre-reqs out of the way; now build RPM list
    queryLocalRpms()
