#! /usr/bin/python
#----------------------------------------------------------------------------------
#
# Copyright 2014,2015,2016,2017,2018,2019 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 json
import cumulus.platforms

#Classes

class ArgParseError(RuntimeError):
    pass

class STATUSLEDRuntimeError(RuntimeError):
    pass
#-------------------------------------------------------------------------------
#
# Functions
#

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

def statusledwarn(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, prog):
    if prog is None:
        myname=os.path.basename(sys.argv[0])
    else:
        myname=os.path.basename(prog)
    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
        if prog is None:
            sys.stderr.write("%s already running as process %s\n" % (myname, oldpid))
        return True
    except Exception as inst:
        raise STATUSLEDRuntimeError("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)

def led_update_units(platform, args):
    units = []
    for unit in platform.statusleds.units:
        cp = unit
        if platform.cpld is not None:
            cp.update_cpld_path(platform.cpld.path)
        if args.display and not args.json:
            loc, color = cp.led_read()
            print '%15s: %s' %(loc, color)
        else:
            units.append(cp)

    if args.json:
        json_output = []
        for unit in units:
            unit.led_update_status()
            loc, color = unit.led_read()
            unit.attrs['color'] = color
            json_output.append(unit.attrs)
        print json.dumps(json_output, indent=4)

    if args.json or args.display:
        exit(0)
    return units

def get_link_state(port):
    if (os.path.exists('/sys/class/net/%s' % port.name_override)):
        return open('/sys/class/net/%s/operstate' % port.name_override).read()
    else:
        return 'down'

def update_port_leds(platform, args):
    ledpath = platform.portleds.led_path
    for port in platform.switch.ports:
        # SCM is in slot-1, while PIM is placed in slot 2-9 on Minipack.
        # So, make slot 1-based to get the actual PIM no.
        slot = str(int(port.label[1])-1)
        portno = port.label[3:]
        portledstatus = ledpath + '/pim%s_port%s_led_status' % (slot, portno)
        portledprofile = ledpath + '/pim%s_port%s_led_profile' % (slot, portno)

        linkstate = get_link_state(port)
        #print 'port %s, operstate %s' %(port.label, linkstate)
        if 'up' in linkstate:
            fh = open(portledstatus, 'w+')
            fh.write('on')
            fh.close()
        else:
            fh = open(portledstatus, 'w+')
            fh.write('off')
            fh.close()

def init_port_leds(platform):
    ledpath = platform.portleds.led_path
    for port in platform.switch.ports:
        # SCM is in slot-1, while PIM is placed in slot 2-9 on Minipack.
        # So, make slot 1-based to get the actual PIM no.
        slot = str(int(port.label[1])-1)
        portno = port.label[3:]
        portledprofile = ledpath + '/pim%s_port%s_led_profile' % (slot, portno)
        fh = open(portledprofile, 'w+')
        fh.write('green')
        fh.close()

def main():
    parser = argparse.ArgumentParser(description="Update Status LEDs on the platform in a loop")
    parser.add_argument('-d', '--display', help ="Display led information and exit",
                        action="store_true")
    parser.add_argument('-i', '--interval', type = int,
                        help ="Update Interval in secs (default is 5 secs)")
    parser.add_argument('-j', '--json', help ="Generate JSON output",
                        action="store_true")

    args = parser.parse_args()

    units = []
    platform = cumulus.platforms.probe()
    interval = 5
    if hasattr(platform.statusleds, 'interval'):
        interval = platform.statusleds.interval
    if hasattr(platform.portleds, 'interval'):
        interval = platform.portleds.interval
    if args.interval:
        interval = args.interval

    if ((platform.statusleds is None) and (platform.portleds is None)):
        units = None
        if args.display:
            print 'LEDs are not managed by ledmgrd on this platform'
            exit(0)
        else:
            syslog.syslog(syslog.LOG_INFO, "LED Manager Daemon doesnt manage leds on this platform")
    elif platform.statusleds is None:
        units = None
        if args.display:
            print 'LEDs are not managed by ledmgrd on this platform'
            exit(0)
        else:
            syslog.syslog(syslog.LOG_INFO, "LED Manager Daemon doesnt manage leds on this platform")
    else:
        units = led_update_units(platform, args)
        syslog.syslog(syslog.LOG_INFO, "Starting LED Manager Daemon")

    pidfile = "/var/run/ledmgrd.pid"
    if already_running(pidfile, None):
        sys.exit(1)
    else:
        file(pidfile, 'w').write(str(os.getpid()))
       
    if platform.portleds is not None:
        #Init port leds profile
        init_port_leds(platform)

    while (True):
        if platform.portleds is not None:
            #process port leds
            update_port_leds(platform, args)

        if platform.statusleds is not None:
            # First check if smond is running
            pidfile = "/var/run/smond.pid"
            if not already_running(pidfile, 'smond'):
                syslog.syslog(syslog.LOG_ERR, 'smond is not running. Start smond and then run ledmgrd')
                sys.exit(1)

            # When smond starts, it deletes "/run/cache/cumulus/unit_state_init_done".
            # Since ledmgrd needs this path to read sensor status, we should wait
            # till this path is created.
            s_path = "/run/cache/cumulus/unit_state_init_done"
            if os.path.isfile(s_path):
                if units:
                    for unit in units:
                        unit.run_state()
            else:
                syslog.syslog(syslog.LOG_WARNING, 'smond is initializing. Skipping setting status LEDs for %d secs' %interval)
        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 = statusledwarn
        syslog.openlog(": %s : " % sys.argv[0])
        exit(main())
    except STATUSLEDRuntimeError, 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)
