#! /usr/bin/python
#-------------------------------------------------------------------------------
#
# Copyright 2013,2015,2016,2018, Cumulus Networks, Inc.  All rights reserved
#
#-------------------------------------------------------------------------------
#

#-------------------------------------------------------------------------------
#
# Imports
#

import syslog
import warnings
import exceptions
import re
import os
import sys
import time
import signal
import traceback
import argparse
import ConfigParser
import cumulus.platforms
import cumulus.sdnotify
import shutil

# Classes
#
class ArgParseError(RuntimeError):
    pass

class SMONDRuntimeError(RuntimeError):
    pass

#-------------------------------------------------------------------------------
#
# Functions
#

#--------------------
#
# warning formats
#

def smondwarn(message, category, filename, lineno, line=None):
    return '%s:%s : %s : %s\n' % (filename, lineno, category.__name__, message)

#--------------------
#
# check to see if an instance is already running
#
def already_running(pidfile):
    myname=os.path.basename(sys.argv[0])
    try:
        if not os.path.isfile(pidfile):
            return False
        oldpid = re.findall('\D*(\d+).*', (file(pidfile, 'r').readline()))[0]
        if not os.path.exists('/proc/%s' % oldpid):
            return False
        if myname not in file('/proc/%s/cmdline' % oldpid, 'r').readline():
            return False
        sys.stderr.write("%s already running as process %s\n" % (myname, oldpid))
        return True
    except Exception as inst:
        raise SMONDRuntimeError("unable to validate pidfile %s: %s" %
                                (pidfile, str(inst)))

#--------------------
#
# normal exit
#
def exit_normally(signum=0, frame=None):
    syslog.syslog(syslog.LOG_INFO, "exiting normally")
    sys.stderr.write("%s : exiting normally\n" % sys.argv[0])
    exit(0)

#
# Main
#
def main():
    interval = 10
    path = '/etc/smond.conf'
    crit_action_path = None
    thres_action_path = None
    config = ConfigParser.ConfigParser()
    if os.path.isfile(path):
        config.read(path)
        if config.has_option("DEFAULT", "interval"):
            interval = int(config.get("DEFAULT", "interval"))
        if config.has_option("DEFAULT", "crit_action_path"):
            crit_action_path = config.get("DEFAULT", "crit_action_path")
        if config.has_option("DEFAULT", "thres_action_path"):
            thres_action_path = config.get("DEFAULT", "thres_action_path")

    parser = argparse.ArgumentParser(description="Monitor sensors data in a loop")
    parser.add_argument('-i', '--interval', type = int,
                        help ="Update Interval in secs (default is %d secs)" %interval)
    args = parser.parse_args()
    if args.interval:
        interval = args.interval

    pidfile = "/var/run/smond.pid"
    if already_running(pidfile):
        sys.exit()
    else:
        file(pidfile, 'w').write(str(os.getpid()))

    p = cumulus.platforms.probe()
    units = []

    if p.sensors is None:
        syslog.syslog(syslog.LOG_INFO, "smond doesnt manage sensors on this platform")
        # This dir is needs to satisfy ledmgrd and smonctl requirements.
        status_path = '/run/cache/cumulus/unit_state'
        if not os.path.exists(status_path):
            os.makedirs(status_path)        
        units = None
    else:
        syslog.syslog(syslog.LOG_INFO, "Monitoring HW Sensors")        
        for unit in p.sensors.units:
            cp = unit
            if os.path.exists(cp.status_path):
                shutil.rmtree(cp.status_path, ignore_errors=True)
            if p.cpld is not None:
                cp.update_cpld_path(p.cpld.path)
            if cp.attrs['type'] == 'temp':
                cp.update_crit_action_path(crit_action_path)
                cp.update_thres_action_path(thres_action_path)
            units.append(cp)

    # This file is created when all sensors have been updated in
    # /run/cache/cumulus/unit_state. ledmgrd is a consumer of this
    # file.
    done_path = '/run/cache/cumulus/unit_state_init_done'
    if os.path.isfile(done_path):
        os.remove(done_path)

    while True:
        if units:
            for unit in units:
                unit.run_state()
                if unit.attrs['state'] != unit.attrs['prev_state']:
                    msg = '%s' %unit.attrs['name']
                    if unit.attrs['name'] != unit.attrs['description']:
                        msg += '(%s)' %unit.attrs['description']
                    msg += ': state changed from %s to %s' \
                        %(unit.attrs['prev_state'], unit.attrs['state'])
                    syslog.syslog(syslog.LOG_WARNING, msg)
                if unit.attrs['msg'] is not None and \
                    unit.attrs['msg'] != unit.attrs['prev_msg']:
                    syslog.syslog(syslog.LOG_WARNING, unit.attrs['msg'])
        if not os.path.isfile(done_path):
            open(done_path, 'a').close()
            # Tell systemd that we are initialized and ready
            # Done after first past, because this is what ledmgrd
            # is looking for, and the file is removed just prior to
            # the main loop being entered
            cumulus.sdnotify.sd_notify(0, "READY=1")
        time.sleep(interval)
    return 0


#--------------------
#
# execution check
#
if __name__ == "__main__":
    try:
        signal.signal(signal.SIGTERM, exit_normally)
        # Cause all warnings to always be triggered.
        warnings.simplefilter("always")
        warnings.formatwarning = smondwarn
        syslog.openlog(": %s : " % sys.argv[0])
        syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_INFO))
        exit(main())
    except SMONDRuntimeError, errstr:
        syslog.syslog(syslog.LOG_ERR, "ERROR : %s" % str(errstr))
        sys.stderr.write("%s : ERROR : %s\n" % (sys.argv[0], str(errstr)))
        exit(1)
    except KeyboardInterrupt:
        exit_normally()
    except Exception:
        (exc_type, exc_value, exc_traceback) = sys.exc_info()
        err = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
        log = 'Unhandled Exception : %s' % err
        syslog.syslog(syslog.LOG_ERR, log)
        sys.stderr.write(log)
