#!/usr/bin/env python
"""
Nagios-friendly script to check status of nl_pipeline
by doing a 'ps' and parsing the output.
The database is checked as well.
"""

import ConfigParser
from optparse import OptionParser
import os
from subprocess import Popen, PIPE
import sys

def read_config(filename):
    parser = ConfigParser.ConfigParser()
    parser.read(filename)
    return parser

class CLError(Exception):
    def __init__(self, msg):
        Exception.__init__(self, "plug-in command line error: %s" % msg)

class DBError(Exception):
    pass

class MyOP(OptionParser):
    """Raise exceptions on usage errors, instead of default behavior
    which exits with the wrong code (2) for Nagios' purposes.
    """
    def error(self, msg):
        raise CLError(msg)
    def exit(self, status=0, msg=None):
        raise CLError(msg)
    def print_help(self):
        OptionParser.print_help(self)
        raise CLError("-h/--help option invoked")

class Result:
    STATUS_NAME = ['OK', 'WARNING', 'CRITICAL', 'UNKNOWN']
    def __init__(self):
        self.status = 0
        self.msg = ""
        self.extra = [ ]

    def report(self):
        print self.STATUS_NAME[self.status] + ': ' + self.msg
        if self.extra:
            print '\n'.join(self.extra)
        
def main():
    parser = MyOP(usage="%prog [-v]")
    parser.add_option('-v', '--verbose', action='count', dest='vb',
                      help="verbosity, repeatable")
    parser.add_option('-c', '--config', dest='cfg', metavar="FILE",
                      help="Read configuration for this script from FILE. "
                      "This will work with MySQL my.cnf files ([client] section). "
                      "(default=%default)", default=os.path.expanduser("~/.my.cnf"))
    opts, args = parser.parse_args()
    vb = min(opts.vb,3)
    result = Result()
    check_ps(result, vb=vb) or check_db(result, vb=vb, cfg=opts.cfg)
    return result

def check_ps(r, vb=0):
    """Check all components via 'ps'.
    """
    r.msg += "all components running"
    cmd = ["ps", "x", "-o", "pid,args"]
    if vb > 1:
        r.extra.append("command = '%s'" % ' '.join(cmd))
    output = Popen(cmd, stdout=PIPE).communicate()[0]
    parts = dict.fromkeys(('nl_parser', 'nl_loader', 'nl_pipeline'), 0)
    for i, line in enumerate(output.split('\n')[1:]):
        fields = line.strip().split(None, 1)
        if vb > 2:
            r.extra.append("line %d = '%s'" % (i, line.strip()))
        if len(fields) < 2:
            if vb > 2:
                r.extra.append(": not enough fields, "
                               "got %d wanted at least %d -- skip" % (
                        len(fields), 2))
            continue
        pid, args = fields
        if vb > 2:
            r.extra.append(": parsed into pid=%s, args=%s" % (
                         pid, args))
        found = False
        for field in args.split():
            base_comm = os.path.basename(field)
            if parts.has_key(base_comm):
                if vb > 2:
                    r.extra.append(": found: %s (%s)" % (base_comm, field))
                parts[base_comm] += 1
                found = True
                break
        if not found:
            if vb > 2:
                r.extra.append(": not a component")
    n = sum(parts.values())
    if n > 3:
        r.msg = "duplicate components running"
        if vb > 0:
            s = "duplicates:"
            for key in parts.keys():
                if parts[key] > 1:
                    s += " %s(%d)" % (key, parts[key])
            r.extra.insert(0, s)
        r.status = 1
    elif n < 3:
        r.msg = "%d components not running" % (3-n,)
        if vb > 0:
            s =  "missing:"
            for key in parts.keys():
                if parts[key] == 0:
                    s += " %s" % key
            r.extra.insert(0, s)
        r.status = 2
    return r.status

def check_db(r, vb=0, cfg=None):
    """Check the database
    """
    import MySQLdb
    r.msg += ", database looks good"
    # get parameters from [database] section of config
    config = read_config(cfg)
    if config.has_section('database'):
        params = config.items('database')
    elif config.has_section('client'):
        params = config.items('client')
    else:
        raise ValueError("In %s: Cannot find section "
                         "[database] or [client] "
                         "with database connection "
	                 "parameters." % cfg)
    d = { 'host' : 'localhost', 'port' : '3306' }
    for keyword, value in params:
        d[keyword] = value
        if vb > 2:
            if keyword == 'password':
                r.extra.append("DB password = xxxx")
            else:
                r.extra.append("DB %s = %s" % (keyword, value))
    # connect using parameters
    try:
        conn = MySQLdb.connect(d['host'], db=d['database'], passwd=d['password'], 
                               user=d['user'], port=int(d['port']))
    except KeyError, E:
        raise DBError("config file %s is missing key: %s" % (cfg,E)) 
    except MySQLdb.Error, E:
        raise DBError("connecting: %s" % E)
    # evaluate constraints
    # (1) major tables are non-empty
    is_error = True 
    for tbl in 'event', None, 'attr':
        # tbl = None divides errors from warnings        
        if tbl == None:
            is_error = False
            continue
        c = conn.cursor()
        stmt = "select count(*) from %s" % tbl
        try:
            if vb > 1:
                r.extra.append("DB statement '%s'" % stmt)
            c.execute(stmt)
        except MySQLdb.Error, E:
            r.msg = "DB error during '%s': %s" % (stmt, E)
            r.status = 2
            return r.status
        row = c.fetchone()
        count = int(row[0])
        if vb > 2:
            r.extra.append(': count = %d' % count)
        if count == 0:
            r.msg = "DB table %s is empty" % tbl
            r.status = (1, 2)[is_error]
            return r.status
    return r.status

if __name__ == '__main__':
    try:
        result = main()
    except Exception, E:
        result = Result()
        result.status = 3
        result.msg = "%s" % E
    result.report()
    sys.exit(result.status)
