#!/usr/bin/python

#-------------------------------------------------------------------------------
#
#   Copyright (C) 2017 Cumulus Networks, Inc. All rights reserved
#
#   This software is subject to the Cumulus Networks End User License
#   Agreement available  at the following locations:
#
#   Internet: https://cumulusnetworks.com/downloads/eula/latest/view/
#   Cumulus Linux systems: /usr/share/cumulus/EULA.txt
#
#-------------------------------------------------------------------------------

"""
This module implements the command line interface to the chassisd service.
"""


#-------------------------------------------------------------------------------
#
#   Import the necessary modules
#
#-------------------------------------------------------------------------------

try:
    import argparse
    import chassisd.chassisd_cmdclient
    import cumulus.chassis
    import signal
    import sys
    import json
except ImportError, e:
    raise ImportError (str(e) + "- required module not found")
except KeyboardInterrupt:
    exit(-1)

#
#   Define constants
#
_ChassisCtlVersion      = "0.1.0"
_ChassisCmdVersion      = "0.1.0"


#
#   Global Variables
#
Parser      = None
ChassisdCmd = None


#-------------------------------------------------------------------------------
#
#   Command Handlers
#
#-------------------------------------------------------------------------------

def HandleEcho():
    jsonStr = ChassisdCmd.run("echo " + " ".join(Parser.args.args))
    try:
        cmdResult = json.loads(jsonStr)
    except ValueError as e:
        print "Invalid output from chassisd: %s\n" % (e,)
        return -1
    retVal = -1 if 'errorMsg' in cmdResult else 0
    if Parser.args.json:
        outStr = jsonStr
    else:
        outStr = "cumulus_chassisd returned : " + cmdResult.get('errorMsg', cmdResult.get('reply', ""))
    print outStr
    return retVal

def HandleLogLevel():
    jsonStr = ChassisdCmd.run("setloglevel " + " ".join(Parser.args.args))
    try:
        cmdResult = json.loads(jsonStr)
    except ValueError as e:
        print "Invalid output from chassisd: %s\n" % (e,)
        return -1
    retVal = -1 if 'errorMsg' in cmdResult else 0
    if Parser.args.json:
        outStr = jsonStr
    else:
        outStr = cmdResult.get('errorMsg', "")
    print outStr
    return retVal

def SlotIdToName(slotId):
    nameList = ["LC1",  "LC2",  "LC3",  "LC4",  "LC5",  "LC6",  "LC7",  "LC8",
                "LC9",  "LC10", "LC11", "LC12", "LC13", "LC14", "LC15", "LC16",
                "FC1",  "FC2",  "FC3",  "FC4",  "FC5",  "FC6",  "FC7",  "FC8"]
    if 1 <= slotId <= 48:
        return nameList[(slotId-1)/2]
    return "???"

def AdminStateToStr(adminState):
    adminStates = { 0 : "Enabled", 1 : "Disabled" }
    return adminStates.get(adminState, "Unknown")

def CtpStateToStr(state):
    states = { 0 : "Blocking", 1 : "Listening", 2 : "Learning", 3 : "Forwarding", 255 : "Disabled" }
    return states.get(state, "Unknown")

def HandleStatus():
    jsonStr = ChassisdCmd.run("getsummary " + " ".join(Parser.args.args))
    try:
        cmdResult = json.loads(jsonStr)
    except ValueError as e:
        print "Invalid output from chassisd: %s\n" % (e,)
        return -1
    retVal = -1 if 'errorMsg' in cmdResult else 0
    if Parser.args.json:
        print jsonStr
    else:
        if 'errorMsg' in cmdResult:
            print cmdResult.get('errorMsg', "")
        elif 'noCtp' in cmdResult:
            reason = "well, actually, I don't know."
            if cmdResult.get('noCtp') == 'cpuB':
                reason = "this is the B-side CPU"
            elif cmdResult.get('noCtp') == 'staticTopo':
                reason = "this card is configured for static topology"
            elif cmdResult.get('noCtp') == 'notRunning':
                reason = "the Accton Chassisd daemon is not running"
            print "CTP information is not availale for this card because %s" % (reason,)
        else:
            ctpData = cmdResult
            print "Admin State: %s" % (AdminStateToStr(ctpData.get("adminState")),)
            print "  CTP State: %s" % (CtpStateToStr(ctpData.get("state")),)
            print ""

            print " " * 21 + "Slot  Priority  Address"
            print " " * 21 + "----  --------  -----------------"

            ourCardId = ctpData.get('brPort',0)
            ourCard = SlotIdToName(ourCardId)
            ourPrio = ctpData.get("brId",{}).get("prio", "")
            ourAddr = ctpData.get("brId",{}).get("addr", "Unknown")
            print "%19s  %-4s  %-8s  %-17s" % ("This card:", ourCard, ourPrio, ourAddr)

            fcCardId = ctpData.get('rootPortId',0)
            fcCard = SlotIdToName(fcCardId)
            if ourCard == fcCard:
                fcCard = fcCard + "*"
            fcPrio = ctpData.get("rootBrId",{}).get("prio", "")
            fcAddr = ctpData.get("rootBrId",{}).get("addr", "Unknown")
            print "%19s  %-4s  %-8s  %-17s" % ("Root fabric card:", fcCard, fcPrio, fcAddr)

            lcCardId = ctpData.get('rootLCPortId',0)
            lcCard = SlotIdToName(lcCardId)
            if ourCard == lcCard:
                lcCard = lcCard + "*"
            lcPrio = ctpData.get("designBrId",{}).get("prio", "")
            lcAddr = ctpData.get("designBrId",{}).get("addr", "Unknown")
            print "%19s  %-4s  %-8s  %-17s" % ("Root line card:", lcCard, lcPrio, lcAddr)
            print ""

            print "     1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16"
            statusStr = "    "
            for be in ctpData.get("brExist", [{}]*48):
                # Only process A-side CPUs
                if be.get('slotId', 0) % 2 == 1:
                    if be.get('slotId', 0) == 1:
                        statusStr = "LC:"
                    elif be.get('slotId', 0) == 33:
                        print statusStr
                        statusStr = "FC:"
                    status = "N"
                    if be.get('age',0) != 0:
                        status = "R" if be.get('slotId') in [fcCardId, lcCardId] else "Y"
                    status += "*" if be.get('slotId') == ourCardId else " "
                    statusStr += "  " + status
            print statusStr
    return retVal

def HandleShowTemps():
    jsonStr = ChassisdCmd.run("gettemps " + " ".join(Parser.args.args))
    try:
        cmdResult = json.loads(jsonStr)
    except ValueError as e:
        print "Invalid output from chassisd: %s\n" % (e,)
        return -1
    retVal = -1 if 'errorMsg' in cmdResult else 0
    if Parser.args.json:
        print jsonStr
    else:
        if 'errorMsg' in cmdResult:
            print cmdResult.get('errorMsg', "")
        elif 'noTemps' in cmdResult:
            reason = "well, actually, I don't know."
            if cmdResult.get('noTemps') == 'notLM':
                reason = "this is not the local master fabric card"
            elif cmdResult.get('noTemps') == 'notRunning':
                reason = "the Accton Chassisd daemon is not running"
            print "Temperature information is not availale for this card because %s" % (reason,)
        else:
            # Get a chassis object for this chassis
            try:
                chassis = cumulus.chassis.probe()
            except (ImportError, RuntimeError, AssertionError) as e:
                print("Unable to determine chassis type: %s" % (e,))
                return -1
            slotId = chassis.GetSlotNum()
            slotToFanCtrl = {}

            if slotId in [33, 35]:
                slotToFanCtrl = { "fc102" : (0, "fca"), "fc202" : (0, "fcb"), "lc501" : (0, "lca"),
                                  "lc601" : (0, "lcb"), "lc701" : (0, "lcc"), "lc801" : (0, "lcd"),
                                  "fc101" : (1, "fca"), "fc201" : (1, "fcb"), "lc101" : (1, "lca"),
                                  "lc201" : (1, "lcb"), "lc301" : (1, "lcc"), "lc401" : (1, "lcd") }
            elif slotId in [37, 39]:
                slotToFanCtrl = { "fc302" : (0, "fca"), "fc402" : (0, "fcb"), "lc102" : (0, "lca"),
                                  "lc202" : (0, "lcb"), "lc302" : (0, "lcc"), "lc402" : (0, "lcd"),
                                  "fc301" : (1, "fca"), "fc401" : (1, "fcb"), "lc502" : (1, "lca"),
                                  "lc602" : (1, "lcb"), "lc702" : (1, "lcc"), "lc802" : (1, "lcd") }
            elif slotId in [41, 43]:
                slotToFanCtrl = { "fc502"  : (0, "fca"), "fc602"  : (0, "fcb"), "lc1301" : (0, "lca"),
                                  "lc1401" : (0, "lcb"), "lc1501" : (0, "lcc"), "lc1601" : (0, "lcd"),
                                  "fc501"  : (1, "fca"), "fc601"  : (1, "fcb"), "lc901"  : (1, "lca"),
                                  "lc1001" : (1, "lcb"), "lc1101" : (1, "lcc"), "lc1201" : (1, "lcd") }
            elif slotId in [45, 47]:
                slotToFanCtrl = { "fc702"  : (0, "fca"), "fc802"  : (0, "fcb"), "lc902"  : (0, "lca"),
                                  "lc1002" : (0, "lcb"), "lc1102" : (0, "lcc"), "lc1202" : (0, "lcd"),
                                  "fc701"  : (1, "fca"), "fc801"  : (1, "fcb"), "lc1302" : (1, "lca"),
                                  "lc1402" : (1, "lcb"), "lc1502" : (1, "lcc"), "lc1602" : (1, "lcd") }

            print "Card       ASIC         CPU         LM75a        LM75b        LM75c        LM75d"
            print "-----  -----------  -----------  -----------  -----------  -----------  -----------"
            for cardName, fanCtrlInfo in sorted(slotToFanCtrl.iteritems()):
                tempString = ""
                tempDict = cmdResult.get(fanCtrlInfo[1],[{},{}])[fanCtrlInfo[0]]
                for tempNode in ["asic", "cpu", "lm75a", "lm75b", "lm75c", "lm75d"]:
                    temps = tempDict.get(tempNode,{})
                    tempString += "  " + "%3s:" % (temps.get("temp", "???"),)
                    tempString += "%3s:" % (temps.get("warn", "???"),)
                    tempString += "%3s" % (temps.get("shutdown", "???"),)
                print cardName + tempString
            print "Temperature data is 'current temp : warning threshold : shutdown threshold' in degrees celsius"

    return retVal

def HandleSetTempThresh():
    jsonStr = ChassisdCmd.run("settempthresh " + " ".join(Parser.args.args))
    try:
        cmdResult = json.loads(jsonStr)
    except ValueError as e:
        print "Invalid output from chassisd: %s\n" % (e,)
        return -1
    retVal = -1 if 'errorMsg' in cmdResult else 0
    if Parser.args.json:
        outStr = jsonStr
    else:
        outStr = cmdResult.get('errorMsg', "")
    print outStr
    return retVal

def HandleGetLocalMasters():
    jsonStr = ChassisdCmd.run("localmasters " + " ".join(Parser.args.args))
    try:
        cmdResult = json.loads(jsonStr)
    except ValueError as e:
        print "Invalid output from chassisd: %s\n" % (e,)
        return -1
    retVal = -1 if 'errorMsg' in cmdResult else 0
    if Parser.args.json:
        print jsonStr
    else:
        if 'errorMsg' in cmdResult:
            print cmdResult.get('errorMsg', "")
        elif 'noLM' in cmdResult:
            reason = "well, actually, I don't know."
            if cmdResult.get('noLM') == 'cpuB':
                reason = "this is the B-side CPU"
            elif cmdResult.get('noLM') == 'notRunning':
                reason = "the Accton Chassisd daemon is not running"
            print "Local Master information is not availale for this card because %s" % (reason,)
        else:
            lmNameList = [ SlotIdToName(lm) for lm in cmdResult if lm != 0]
            if lmNameList:
                print "Local masters: %s" % (" ".join(lmNameList))
            else:
                print "No local masters"
    return retVal


#-------------------------------------------------------------------------------
#
#   Command Dictionary - This dictionary lists all of the command strings as
#   the dictionary keys, and the function which handles the execution of the
#   command as the value.
#   Commands that are setup as "Internal" are not displayed in the help menu.
#   Internal commands can be deprecated and output/display format can be 
#   changed without notice.
#-------------------------------------------------------------------------------

chassisCtlCmds = {
    #Command            : ( Function, Help, Internal)
    "echo"              : ( HandleEcho, "Echo back the supplied string", False),
    "loglevel"          : ( HandleLogLevel, "Sets the logging level", False),
    "status"            : ( HandleStatus, "Display the status of the chassisd daemon", False),
    "showtemps"         : ( HandleShowTemps, "Display the temperature sensors from a master fabric card", False),
    "settempthresh"     : ( HandleSetTempThresh, "Set a temperature threshold: '<card> <sensor> [warn|shut] <value>'", False),
    "localmasters"      : ( HandleGetLocalMasters, "Display the local master fabric cards", False),
}


#-------------------------------------------------------------------------------
#
#   Command line parsing code
#
#-------------------------------------------------------------------------------

class ChassisCtlParser:

    def __init__(self):
        '''
        Create the parser
        '''
        epilogStr = "The commands are:\n"
        for cmd in sorted(chassisCtlCmds.iterkeys()):
            if not chassisCtlCmds[cmd][2]:
                epilogStr += "%-20s  %s\n" % (cmd, chassisCtlCmds[cmd][1])
        epilogStr += "\nSee the chassisctl man page for more information"
        self.parser = argparse.ArgumentParser(description="Chassis daemon control interface, version %s" % (_ChassisCtlVersion,),
                                              usage='%(prog)s [-h] [-j] [-v] [command [args]]', epilog=epilogStr, 
                                              formatter_class=argparse.RawDescriptionHelpFormatter)
        self.parser.add_argument("command", choices=chassisCtlCmds.keys(), default="status", nargs='?',
                                 metavar="command", help="Command to execute, default is 'status'")
        self.parser.add_argument("-v", "--verbose", action="store_true", default=False,
                                 help="Increase the amount of output.")
        self.parser.add_argument("-j", "--json", action="store_true", default=False,
                                 help="json output.")
        self.parser.add_argument("args", nargs=argparse.REMAINDER, help="Additional command parameters")

    def ParseCmdLine(self):
        '''
        Parse the command line parameters
        '''
        try:
            self.args = self.parser.parse_args()
        except:
            sys.exit(-1)


#-------------------------------------------------------------------------------
#
#   Main program entry point
#
#-------------------------------------------------------------------------------

def main():

    # Parse the command line parameters
    global Parser
    Parser = ChassisCtlParser()
    Parser.ParseCmdLine()

    # Handle broken pipe problems
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)

    # Create a connection to clagd
    global ChassisdCmd
    try:
        ChassisdCmd = chassisd.chassisd_cmdclient.chassisd_cmdclient()
    except IOError as e:
        errMsg = "Unable to communicate with chassisd. Is it running?"
        if Parser.args.json:
            print json.dumps({"errorMsg" : errMsg})
        else:
            print errMsg
        return -1
    except ValueError as e:
        errMsg = "Unable to communicate with chassisd. Is it running?"
        if Parser.args.json:
            print json.dumps({"errorMsg" : errMsg})
        else:
            print errMsg
        return -1

    # Execute the command
    try:
        return chassisCtlCmds[Parser.args.command][0]()
    except IOError as e:
        errMsg = "*** Command interrupted (%s). Output incomplete." % (e,)
        if Parser.args.json:
            print json.dumps({"errorMsg" : errMsg})
        else:
            print errMsg
        ChassisdCmd.close()
    except KeyboardInterrupt:
        errMsg = "*** Command interrupted. Output incomplete."
        if Parser.args.json:
            print json.dumps({"errorMsg" : errMsg})
        else:
            print errMsg
        ChassisdCmd.close()

    return -1



#-------------------------------------------------------------------------------
#
#   Are we being executed or imported?
#
#-------------------------------------------------------------------------------

if __name__ == '__main__':
    rc = main()
    sys.exit(rc)

