#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-

import simplejson as json

################################################################################
# __      __        _       _     _      
# \ \    / /       (_)     | |   | |     
#  \ \  / /_ _ _ __ _  __ _| |__ | | ___ 
#   \ \/ / _` | '__| |/ _` | '_ \| |/ _ \
#    \  / (_| | |  | | (_| | |_) | |  __/
#     \/ \__,_|_|  |_|\__,_|_.__/|_|\___|
#                                        
################################################################################

filter_ZeroVVCPG = True
filter_NoSystemVols = True

threepar_default_levels = {}
factory_settings["threepar_default_levels"] = {}

threepar_flashcache_default_levels = {}
factory_settings["threepar_flashcache_default_levels"] = {
    "flashcache_state_warning"   : 2,
    "flashcache_state_critical"  : 3,
    "levels" : ( 80.0, 90.0 ),
}

threepar_cpgs_default_levels = {}
factory_settings["threepar_cpgs_default_levels"] = {
    "cpgstatus_warning"  : 2,
    "cpgstatus_critical" : 3,
    "levels" : ( 80.0, 90.0 ),
}

threepar_volumes_default_levels = {}
factory_settings["threepar_volumes_default_levels"] = {
    "volstatus_warning"  : 2,
    "volstatus_critical" : 3,
    "levels" : ( 80.0, 90.0 ),
    "compaction_grade" : ( 0.5, 0.2 ),
}

threepar_ports_default_levels = {}
factory_settings["threepar_ports_default_levels"] = {
    "linkState-1"     : 1,
    "linkState-2"     : 1,
    "linkState-3"     : 1,
    "linkState-4"     : 0,
    "linkState-5"     : 1,
    "linkState-6"     : 2,
    "linkState-7"     : 1,
    "linkState-8"     : 0,
    "linkState-9"     : 1,
    "linkState-10"    : 1,
    "linkState-11"    : 1,
    "linkState-12"    : 1,
    "linkState-13"    : 1,
    "linkState-14"    : 1,
    "failoverState-1" : 0,
    "failoverState-2" : 2,
    "failoverState-3" : 2,
    "failoverState-4" : 2,
    "failoverState-5" : 2,
    "failoverState-6" : 2,
    "failoverState-7" : 1,
}

threepar_degraded_stateIds = {
    1: "SA_LIMIT_REACHED", # Administrative space is at limit.
    2: "SD_LIMIT_REACHED", # Copy space is at limit.
    3: "SA_GROW_FAILED", # Administrative space grow failed.
    4: "SD_GROW_FAILED", # Copy space grow failed.
    5: "SA_WARN_REACHED", # Administrative space is at warning level.
    6: "SD_WARN_REACHED", # Copy space at warning level.
    7: "INVALID", # Invalid
}
threepar_degraded_stateDescriptions = {
    1: "Administrative space is at limit",
    2: "Copy space is at limit",
    3: "Administrative space grow failed",
    4: "Copy space grow failed",
    5: "Administrative space is at warning level",
    6: "Copy space at warning level",
    7: "Invalid",
}

def get_3par_degradedStates( states ):
    if len(states) <= 0:
        return ""

    desc = ""

    for id in states:
        desc += "%s\\n" % get_3par_stateDescription( id )

    return desc

def get_3par_stateIds( id ):
    desc = "UNKNOWN"

    if id in threepar_degraded_stateIds:
        return threepar_degraded_stateIds[id]

    return desc

def get_3par_stateDescription( id ):
    desc = "Unknown"

    if id in threepar_degraded_stateIds:
        return threepar_degraded_stateDescriptions[id]

    return desc

################################################################################
#   _____                                      
#  / ____|                                     
# | |     ___  _ __ ___  _ __ ___   ___  _ __  
# | |    / _ \| '_ ` _ \| '_ ` _ \ / _ \| '_ \ 
# | |___| (_) | | | | | | | | | | | (_) | | | |
#  \_____\___/|_| |_| |_|_| |_| |_|\___/|_| |_|
#
################################################################################

def parse_3par(info):
    a = [item for sublist in info for item in sublist]
    b = ''.join(a)
    parsed = json.loads(b)
    return parsed

def get_vlunstats_data_host( parsed, host ):

    if "sysrep_vlunstatistics" not in parsed:
        return {}

    if "members" not in parsed["sysrep_vlunstatistics"]:
        return {}

    data = []

    for entry in parsed["sysrep_vlunstatistics"]["members"]:
        if "hostname" not in entry:
            continue

        if entry["hostname"] == host:
            data.append( entry )

    return data


################################################################################
#    _____           _                 
#  / ____|         | |                
# | (___  _   _ ___| |_ ___ _ __ ___  
#  \___ \| | | / __| __/ _ \ '_ ` _ \ 
#  ____) | |_| \__ \ ||  __/ | | | | |
# |_____/ \__, |___/\__\___|_| |_| |_|
#          __/ |                      
#         |___/
#         
################################################################################

def inventory_3par(parsed):
    return [ ( "SystemInfos", {} ) ]

def check_3par(item, params, parsed):
    state       = 3
    infotext    = "No item implemented"

    state    = 0                 # TODO: echten State übergeben
    infotext = ""

    systemInfos = parsed["system"]

    infotext = "%s Node %s (SN: %s / V: %s)" % ( systemInfos["totalNodes"], systemInfos["model"], systemInfos["serialNumber"], systemInfos["systemVersion"] )

    # TODO: what if node removed?
    if len(systemInfos["onlineNodes"]) < systemInfos["totalNodes"]:
        state = 2
        infotext = "Only %s of %s nodes online!" % ( systemInfos["onlineNodes"], systemInfos["totalNodes"] )
    else:
        state = 0

    return ( state, infotext )

check_info["3par"] = {
    'default_levels_variable':  "threepar_default_levels",
    'check_function':           check_3par,
    'inventory_function':       inventory_3par,
    'parse_function':           parse_3par,
    'service_description':      'System Status - %s',
    "has_perfdata"        :     False,
    'group':                    'Storage',
}

################################################################################
# Total Capacity
################################################################################

def inventory_3par_systemtotalcap(parsed):
    return [ ( "Total Capacity", {} ) ]

def check_3par_systemtotalcap(item, params, parsed):
    state       = 3
    infotext    = "No item implemented"
    perfdata    = []

    state    = 0                 # TODO: echten State übergeben
    infotext = ""

    systemInfos = parsed["system"]
    
    used_mb = systemInfos["allocatedCapacityMiB"] * 1.048576
    size_mb = systemInfos["totalCapacityMiB"] * 1.048576
    avail_mb = size_mb -used_mb
    
    state, infotext, perfdata = df_check_filesystem_single("3Par", "totalCapacity", size_mb, avail_mb, None, None, None, params)

    return ( state, infotext, perfdata )

check_info["3par.SystemTotalCapacity"] = {
    "group"                   : "filesystem",
    'default_levels_variable':  "filesystem_default_levels",
    'check_function':           check_3par_systemtotalcap,
    'inventory_function':       inventory_3par_systemtotalcap,
    'parse_function':           parse_3par,
    'service_description':      '%s',
    "has_perfdata"        :     True,
}


################################################################################
# Total failed capacity
################################################################################
def inventory_3par_systemfailedcap(parsed):
    return [ ( "Total Failed", {} ) ]

def check_3par_systemfailedcap(item, params, parsed):
    state       = 3
    infotext    = "No item implemented"
    perfdata    = []

    state    = 0                 # TODO: echten State übergeben
    infotext = ""

    systemInfos = parsed["system"]

    used_mb = systemInfos["failedCapacityMiB"] * 1.048576
    size_mb = systemInfos["totalCapacityMiB"] * 1.048576
    avail_mb = size_mb -used_mb
    state, infotext, perfdata = df_check_filesystem_single("3Par", "failedCapacity", size_mb, avail_mb, None, None, None, params)

    return ( state, infotext, perfdata )

check_info["3par.SystemFailedCapacity"] = {
    "group"                   : "filesystem",
    "default_levels_variable" : "filesystem_default_levels",
    'check_function':           check_3par_systemfailedcap,
    'inventory_function':       inventory_3par_systemfailedcap,
    'parse_function':           parse_3par,
    'service_description':      '%s',
    "has_perfdata"        :     True,
}

##############################################
#  _    _           _       
# | |  | |         | |      
# | |__| | ___  ___| |_ ___ 
# |  __  |/ _ \/ __| __/ __|
# | |  | | (_) \__ \ |_\__ \
# |_|  |_|\___/|___/\__|___/
#                           
##############################################


hp3par_hosts_default_levels = {}
factory_settings["hp3par_hosts_default_levels"] = {
    "hostPortLevels": ( 3, 2 ),
    "serviceTimeMSLevels": ( 30, 50 ),
}


def inventory_3par_hosts(parsed):
    inventory = []

    if "hosts" not in parsed:
        return inventory

    for host in parsed["hosts"]["members"]:
        inventory.append( ( host["name"], None ) )

    return inventory

def check_3par_hosts(item, params, parsed):
    state       = 3
    infotext    = "No item implemented"
    perfdata    = []

    if "hosts" not in parsed:
        return ( state, infotext, perfdata )

    statsdata = get_vlunstats_data_host( parsed, item )
    
    for host in parsed["hosts"]["members"]:
        if item == host["name"]:
            state = 0

            portCount = get_hostports_state( item, params, parsed )
            if portCount <= params["hostPortLevels"][1]:
                state = max( state, 2 )
                infotext = "Only %s Ports (!!)" % ( portCount )
            elif portCount <= params["hostPortLevels"][0]:
                state = max( state, 1 )
                infotext = "Only %s Ports (!)" % ( portCount )
            else:
                infotext = "%s Ports" % ( portCount )

            # TODO: levels
            # TODO: _min, _avg, IOSizeKB

            serviceTimeMS_total_max = 0
            IO_total_sum            = 0
            KBytes_total_sum        = 0
            queueLength_sum         = 0

            if len(statsdata) > 0:
                for entry in statsdata: 
                    serviceTimeMS_total_max = max( entry["serviceTimeMS"]["total"], serviceTimeMS_total_max )
                    IO_total_sum += entry["IO"]["total"]
                    KBytes_total_sum += entry["KBytes"]["total"]
                    queueLength_sum += entry["queueLength"]

                if serviceTimeMS_total_max >= params["serviceTimeMSLevels"][1]:
                    state = max( state, 2 )
                    infotext += " - max %s ms (!!)" % ( serviceTimeMS_total_max )
                elif serviceTimeMS_total_max >= params["serviceTimeMSLevels"][0]:
                    state = max( state, 1 )
                    infotext += " - max %s ms (!)" % ( serviceTimeMS_total_max )
                else:
                    state = max( state, 0 )
                    infotext += " - max %s ms" % ( serviceTimeMS_total_max )

                perfdata.append( ( "serviceTimeMS_total_max", serviceTimeMS_total_max, params["serviceTimeMSLevels"][0], params["serviceTimeMSLevels"][1] ) )
                perfdata.append( ( "IO_total_sum", IO_total_sum ) )
                perfdata.append( ( "KBytes_total_sum", KBytes_total_sum ) )
                perfdata.append( ( "queueLength_sum", queueLength_sum ) )

            if state < 0:
                state = 3
            return ( state, infotext, perfdata )

def get_hostports_state(host, params, parsed):
    portCount  = 0

    if "ports" not in parsed:
        return portCount

    for port in parsed["ports"]["members"]:

        if "device" not in port:
            return portCount

        if host in port["device"]:
            portCount += 1

    return portCount

check_info["3par.hosts"] = {
    'default_levels_variable':  "hp3par_hosts_default_levels",
    'check_function':           check_3par_hosts,
    'inventory_function':       inventory_3par_hosts,
    'parse_function':           parse_3par,
    'service_description':      'Host Status - %s',
    "has_perfdata"        :     True,
    'group':                    '3par_hosts',
}

##############################################
#   _____ _____   _____     
#  / ____|  __ \ / ____|    
# | |    | |__) | |  __ ___ 
# | |    |  ___/| | |_ / __|
# | |____| |    | |__| \__ \
#  \_____|_|     \_____|___/
#                           
##############################################

def inventory_3par_cpgs(parsed):
    inventory = []

    if "cpgs" not in parsed:
        return inventory

    for cpg in parsed["cpgs"]["members"]:
        totalVVs = ( cpg["numFPVVs"] + cpg["numTDVVs"] + cpg["numTPVVs"] )      # TODO: precompute? store?
        if filter_ZeroVVCPG and totalVVs <= 0:                                  # TODO: inventory parameters
            continue
        inventory.append( ( cpg["name"], {} ) )

    return inventory

def check_3par_cpgs(item, params, parsed):
    state       = -1
    infotext    = "No item implemented"
    perfdata    = []

    if "cpgs" not in parsed:
        return ( state, infotext, perfdata )

    for cpg in parsed["cpgs"]["members"]:
        if item == cpg["name"]:
            totalVVs = ( cpg["numFPVVs"] + cpg["numTDVVs"] + cpg["numTPVVs"] )

            used_mb = cpg["UsrUsage"]["usedMiB"] * 1.048576
            size_mb = cpg["UsrUsage"]["totalMiB"] * 1.048576
            if size_mb > 0:
                used_perc = 100.0 * (float(used_mb) / size_mb)
                size_gb   = size_mb / 1024.0
                used_hr = get_bytes_human_readable(used_mb * 1024 * 1024)
                size_hr = get_bytes_human_readable(size_mb * 1024 * 1024)
            else:
                used_perc   = 0
                size_gb     = 0
                used_hr     = "0 MB"
                size_hr     = "0 MB"

            # If both numbers end with both MB or GB or TB, then drop the first one
            if used_hr[-2:] == size_hr[-2:]:
                used_hr = used_hr[:-3]

            levels = get_filesystem_levels("bla", "cpg", size_gb, params)
            warn_mb, crit_mb       = levels["levels_mb"]

            if size_mb > 0:
                if warn_mb <= -1:
                    #Negativ levels, so calculate mb left
                    rest_mb = size_mb - used_mb
                    crit_mb = crit_mb * -1
                    warn_mb = warn_mb * -1
                    if rest_mb <= crit_mb:
                        state = max(2, state)
                    elif rest_mb <= warn_mb:
                        state = max(1, state)
                    else:
                        state = max(0, state)
                else:
                    if used_mb >= crit_mb:
                        state = max(2, state)
                    elif used_mb >= warn_mb:
                        state = max(1, state)
                    else:
                        state = max(0, state)

            if cpg["state"] >= levels["cpgstatus_critical"]:
                state = max(2, state)

            elif cpg["state"] >= levels["cpgstatus_warning"]:
                state = max(1, state)
            else:
                state = max(0, state)

            if cpg["state"] >= 3:
                stateWord = "FAILED (!!)"
            elif cpg["state"] >= 2:
                stateWord = "DEGRADED (!)"
            elif cpg["state"] == 1:
                stateWord = "NORMAL"
            else:
                stateWord = "UNKNOWN"

            stateInfo = get_3par_degradedStates(cpg["degradedStates"])

            infotext  = "%s - %s VVs" % ( stateWord, totalVVs )
            
            if used_perc > 0:
                perc_precision = max(1, 2 - int(round(math.log(used_perc, 10))))
            else:
                perc_precision = 1
            infotext += " - %%.%df%%%% used (%%s of %%s), %%s" % perc_precision % (used_perc, used_hr, size_hr, levels["levels_text"])

            perfdata.append( ( "UsrUsedMiB", cpg["UsrUsage"]["usedMiB"], warn_mb, crit_mb, 0, cpg["UsrUsage"]["totalMiB"] ) )

            if state < 0:
                state = 3

            if stateInfo != "":
                infotext += "\\nDegraded (!):\\n%s" %( stateInfo )

            return ( state, infotext, perfdata )

check_info["3par.cpgs"] = {
    'group':                    '3par_cpgs',
    'default_levels_variable':  "threepar_cpgs_default_levels",
    'check_function':           check_3par_cpgs,
    'inventory_function':       inventory_3par_cpgs,
    'parse_function':           parse_3par,
    'service_description':      'CPG Status - %s',
    "has_perfdata"        :     True,
    "includes"                : [ "df.include" ],
}


##############################################
#  ______ _           _                    _          
# |  ____| |         | |                  | |         
# | |__  | | __ _ ___| |__   ___ __ _  ___| |__   ___ 
# |  __| | |/ _` / __| '_ \ / __/ _` |/ __| '_ \ / _ \
# | |    | | (_| \__ \ | | | (_| (_| | (__| | | |  __/
# |_|    |_|\__,_|___/_| |_|\___\__,_|\___|_| |_|\___|
#
##############################################

def inventory_3par_flashcache(parsed):
    inventory = []

    if "flashcache" not in parsed:
        return inventory

    if "code" in parsed["flashcache"] and parsed["flashcache"]["code"] == 285:
        return inventory

    return [ ( "FlashCache", {} ) ]

def check_3par_flashcache(item, params, parsed):
    state    = 0
    infotext = ""
    perfdata = []

    if parsed["flashcache"]["mode"] != 2:
        infotext = "Simulator Mode!"

    if parsed["flashcache"]["state"] >= params["flashcache_state_critical"]:
        state = max( 2, state)
    elif parsed["flashcache"]["state"] >= params["flashcache_state_warning"]:
        state = max( 1, state)
    else:
        state = max( 0, state)

    if parsed["flashcache"]["state"] == 3:
        infotext = "State Failed - " + infotext
    elif parsed["flashcache"]["state"] == 2:
        infotext = "State Degraded - " + infotext
    else:
        infotext = "State Normal -" + infotext

    levels = get_filesystem_levels("bla", "flashcache", parsed["flashcache"]["sizeGiB"], params)
    warn_mb, crit_mb = levels["levels_mb"]
    infotext = infotext + " %s GiB of %s GiB %s" % ( parsed["flashcache"]["usedSizeGiB"], parsed["flashcache"]["sizeGiB"], levels["levels_text"] )

    if parsed["flashcache"]["usedSizeGiB"] > ( crit_mb / 1024 ):
        state = max( 2, state)
    elif parsed["flashcache"]["usedSizeGiB"] > ( warn_mb / 1024 ):
        state = max( 1, state)
    else:
        state = max( 0, state)

    perfdata.append( ( "used", parsed["flashcache"]["usedSizeGiB"], ( warn_mb / 1024 ), ( crit_mb / 1024 ), 0, parsed["flashcache"]["sizeGiB"] ) )

    return ( state, infotext, perfdata )

check_info["3par.flashcache"] = {
    'default_levels_variable':  "threepar_flashcache_default_levels",
    'check_function':           check_3par_flashcache,
    'inventory_function':       inventory_3par_flashcache,
    'parse_function':           parse_3par,
    'service_description':      'Status - %s',
    "has_perfdata"        :     True,
    'group':                    '3par_flashcache',
}

##############################################
#  __      __   _                           
#  \ \    / /  | |                          
#   \ \  / /__ | |_   _ _ __ ___   ___  ___ 
#    \ \/ / _ \| | | | | '_ ` _ \ / _ \/ __|
#     \  / (_) | | |_| | | | | | |  __/\__ \
#      \/ \___/|_|\__,_|_| |_| |_|\___||___/
#
##############################################

def inventory_3par_vols(parsed):
    inventory = []
    if "volumes" in parsed:
        for vol in parsed["volumes"]["members"]:
            if filter_NoSystemVols and vol["policies"]["system"] == True:
                continue

            inventory.append( ( vol["name"], None ) )

    return inventory

def get_sysrep_vlunstatistics(vlun, parsed):
    stats = {}
    for member in parsed["sysrep_vlunstatistics"]["members"]:
        if member["volumeName"] != vlun:
            continue

        memberid = "%s:%s:%s:%s" % ( member["node"], member["slot"], member["cardPort"], member["hostname"] )
        stats[memberid] = member

    values   = {}
    IO_read  = 0
    IO_write = 0
    IO_total = 0
    serviceTimeMS_read_max = 0
    serviceTimeMS_write_max = 0
    serviceTimeMS_total_max = 0

    for member in stats:
        IO_read  += stats[member]["IO"]["read"]
        IO_write += stats[member]["IO"]["write"]
        IO_total += stats[member]["IO"]["total"]
        serviceTimeMS_read_max  = max( serviceTimeMS_read_max, stats[member]["serviceTimeMS"]["read"] )
        serviceTimeMS_write_max = max( serviceTimeMS_write_max, stats[member]["serviceTimeMS"]["write"] )
        serviceTimeMS_total_max = max( serviceTimeMS_total_max, stats[member]["serviceTimeMS"]["total"] )

    values = {
        'IO':   { 'read': IO_read, 'write': IO_write, 'total': IO_total },
        'serviceTimeMS' : { 'read_max': serviceTimeMS_read_max, 'write_max': serviceTimeMS_write_max, 'total_max': serviceTimeMS_total_max }
    }

    return values

def check_3par_vols(item, params, parsed):
    state       = -1
    infotext    = ""
    perfdata    = []

    if "volumes" in parsed:
        for vol in parsed["volumes"]["members"]:
            if item != vol["name"]:
                continue

            vstate = vol["state"] - 1
            used_mb = vol["userSpace"]["usedMiB"] * 1.048576
            size_mb = vol["sizeMiB"] * 1.048576
            avail_mb = size_mb -used_mb
            warn_grade = params["compaction_grade"][0]
            crit_grade = params["compaction_grade"][1]

            state, infotext, perfdata = df_check_filesystem_single("3Par", "volume", size_mb, avail_mb, None, None, None, params)

            if vstate >= params["volstatus_critical"]:
                state = max(2, state)
            elif vstate >= params["volstatus_warning"]:
                state = max(1, state)
            else:
                state = max(0, state)

            if vol["state"] >= 3:
                stateWord = "FAILED"
            elif vol["state"] >= 2:
                stateWord = "DEGRADED"
            elif vol["state"] == 1:
                stateWord = "NORMAL"
            else:
                stateWord = "UNKNOWN"

            infotext = "%s - ID %s - %s" % ( stateWord, vol["baseId"], infotext )

            perfdata.append( ( "Compaction", vol["capacityEfficiency"]["compaction"], warn_grade, crit_grade ) )

            if "sysrep_vlunstatistics" in parsed:
                stats = get_sysrep_vlunstatistics( item, parsed )

                infotext += " - max. %s ms" % ( stats['serviceTimeMS']['read_max'] )

                perfdata.append( ( "IO_read", stats['IO']['read'] ) )
                perfdata.append( ( "IO_write", stats['IO']['write'] ) )
                perfdata.append( ( "IO_total", stats['IO']['total'] ) )
                perfdata.append( ( "serviceTimeMS_read_max", stats['serviceTimeMS']['read_max'] ) )
                perfdata.append( ( "serviceTimeMS_write_max", stats['serviceTimeMS']['write_max'] ) )
                perfdata.append( ( "serviceTimeMS_total_max", stats['serviceTimeMS']['total_max'] ) )

            return ( state, infotext, perfdata )

    if infotext == "" or state < 0:
        state = 3
        infotext = "Not implemented"

    return ( state, infotext, perfdata )

check_info["3par.vols"] = {
    'default_levels_variable':  "threepar_volumes_default_levels",
    'check_function':           check_3par_vols,
    'inventory_function':       inventory_3par_vols,
    'parse_function':           parse_3par,
    'service_description':      'Vol Status - %s',
    "has_perfdata"        :     True,
    'group':                    '3par_volumes',
    #"includes"                : [ "df.include" ],
}


##############################################
#    _____           _       
#   |  __ \         | |      
#   | |__) |__  _ __| |_ ___ 
#   |  ___/ _ \| '__| __/ __|
#   | |  | (_) | |  | |_\__ \
#   |_|   \___/|_|   \__|___/
# 
##############################################

def inventory_3par_ports(parsed):
    inventory = []

    if "ports" in parsed:
        for port in parsed["ports"]["members"]:
            if "label" in port and "portWWN" in port:
                inventory.append( ( port["label"] + "-" + port["portWWN"], None ) )
            elif "label" in port:
                inventory.append( ( port["label"], None ) )
            elif "portWWN" in port:
                inventory.append( ( port["portWWN"], None ) )

    return inventory

def check_3par_ports(item, params, parsed):
    if "ports" in parsed:
        for port in parsed["ports"]["members"]:
            state       = -1
            infotext    = "No item implemented"
            perfdata    = []
            portName    = ""
            portPos     = ""

            if "label" in port and "portWWN" in port:
                portName = port["label"] + "-" + port["portWWN"]
            elif "label" in port:
                portName = port["label"]
            elif "portWWN" in port:
                portName = port["portWWN"]

            if item != portName:
                continue

            if "protocol" in port:
                if port["protocol"] == 1:   infotext = "FC"
                elif port["protocol"] == 2: infotext = "iSCSI"
                elif port["protocol"] == 3: infotext = "FCOE"
                elif port["protocol"] == 4: infotext = "IP"
                elif port["protocol"] == 5: infotext = "SAS"

            if "mode" in port:
                if port["mode"] == 1:   infotext += " / SUSPENDED"
                elif port["mode"] == 2: infotext += " / TARGET"
                elif port["mode"] == 3: infotext += " / INITIATOR"
                elif port["mode"] == 4: infotext += " / PEER"

            if "linkState" in port:
                paramKey = "linkState-%s" % ( port["linkState"] )
                state = max( state, params[paramKey] )

                if port["linkState"] == 1:      infotext += " - CONFIG_WAIT"
                elif port["linkState"] == 2:    infotext += " - ALPA_WAIT"
                elif port["linkState"] == 3:    infotext += " - LOGIN_WAIT"
                elif port["linkState"] == 4:    infotext += " - READY"
                elif port["linkState"] == 5:    infotext += " - LOSS_SYNC"
                elif port["linkState"] == 6:    infotext += " - ERROR_STATE"
                elif port["linkState"] == 7:    infotext += " - XXX"
                elif port["linkState"] == 8:    infotext += " - NONPARTICIPATE"
                elif port["linkState"] == 9:    infotext += " - COREDUMP"
                elif port["linkState"] == 10:   infotext += " - OFFLINE"
                elif port["linkState"] == 11:   infotext += " - FWDEAD"
                elif port["linkState"] == 12:   infotext += " - IDLE_FOR_RESET"
                elif port["linkState"] == 13:   infotext += " - DHCP_IN_PROGRESS"
                elif port["linkState"] == 14:   infotext += " - PENDING_RESET"

            if "failoverState" in port:
                paramKey = "failoverState-%s" % ( port["failoverState"] )
                state = max ( state, params[paramKey] )

                if port["failoverState"] == 1:      infotext += ""
                elif port["failoverState"] == 2:    infotext += " - FAILOVER_PENDING"
                elif port["failoverState"] == 3:    infotext += " - FAILED_OVER"
                elif port["failoverState"] == 4:    infotext += " - Failover ACTIVE"
                elif port["failoverState"] == 5:    infotext += " - Failover ACTIVE_DOWN"
                elif port["failoverState"] == 6:    infotext += " - Failover ACTIVE_FAILED"
                elif port["failoverState"] == 7:    infotext += " - FAILBACK_PENDING"

            if "portPos" in port:
                portPos = "%s:%s:%s" % ( port["portPos"]["node"], port["portPos"]["slot"], port["portPos"]["cardPort"] )
                infotext += " - %s" % ( portPos )

            if "partnerPos" in port:
                infotext += " (%s:%s:%s)" % ( port["partnerPos"]["node"], port["partnerPos"]["slot"], port["partnerPos"]["cardPort"] )

            if portPos != "" and "sysrep_portstatistics" in parsed:
                stats = get_sysrepstats_port( portPos, parsed )
                if 'serviceTimeMS' in stats:
                    infotext += " - %s ms" % ( stats["serviceTimeMS"]["total"] )
                    perfdata.append( ( "serviceTimeMS_read", stats["serviceTimeMS"]["read"] ) )
                    perfdata.append( ( "serviceTimeMS_write", stats["serviceTimeMS"]["write"] ) )
                    perfdata.append( ( "serviceTimeMS_total", stats["serviceTimeMS"]["total"] ) )
                if 'IO' in stats:
                    perfdata.append( ( "IO_read", stats["IO"]["read"] ) )
                    perfdata.append( ( "IO_write", stats["IO"]["write"] ) )
                    perfdata.append( ( "IO_total", stats["IO"]["total"] ) )
                if 'IOSizeKB' in stats:
                    perfdata.append( ( "IOSizeKB_read", stats["IOSizeKB"]["read"] ) )
                    perfdata.append( ( "IOSizeKB_write", stats["IOSizeKB"]["write"] ) )
                    perfdata.append( ( "IOSizeKB_total", stats["IOSizeKB"]["total"] ) )
                if 'KBytes' in stats:
                    perfdata.append( ( "KBytes_read", stats["KBytes"]["read"] ) )
                    perfdata.append( ( "KBytes_write", stats["KBytes"]["write"] ) )
                    perfdata.append( ( "KBytes_total", stats["KBytes"]["total"] ) )
                if 'busyPct' in stats:
                    perfdata.append( ( "busyPct", stats["busyPct"] ) )
                if 'queueLength' in stats:
                    perfdata.append( ( "queueLength", stats["queueLength"] ) )

            if state == -1:
                state = 3

            return ( state, infotext, perfdata )

    if state == -1:
        state = 3
    return ( state, infotext, perfdata )

def get_sysrepstats_port(portPos, parsed):
    for member in parsed["sysrep_portstatistics"]["members"]:
        memberPos = "%s:%s:%s" % ( member["node"], member["slot"], member["cardPort"] )
        if memberPos != portPos:
            continue

        return member

    return {}

check_info["3par.ports"] = {
    'default_levels_variable':  "threepar_ports_default_levels",
    'check_function':           check_3par_ports,
    'inventory_function':       inventory_3par_ports,
    'parse_function':           parse_3par,
    'service_description':      'Port Status - %s',
    "has_perfdata"        :     True,
    'group':                    '3par_ports',
}




##############################################
#
#   Remote Copy Groups
# 
##############################################

hp3par_rcopyGroupStateEnum = {
    1 : "new",
    2 : "starting",
    3 : "started",
    4 : "restart",
    5 : "stopped",
    6 : "backup",
    7 : "failsave",
    8 : "unknown",
    9 : "logging",
}

hp3par_rcopyGroupModeEnum = {
    1 : "sync",
    2 : "periodic",
    3 : "periodic",
    4 : "async",
}

hp3par_rcopyGroupVVStatusEnum =  {
    1 : "new",
    2 : "syncing",
    3 : "synced",
    4 : "unsync",
    5 : "stale",
    6 : "newpresynced",
    7 : "newsyncedfromsnap",
    8 : "stopped",
    9 : "failsave",
    10 : "unknown",
    11 : "logging",
}

hp3par_rcopyGroupState_default_levels = {}
factory_settings["hp3par_rcopyGroupState_default_levels"] = {
    "groupState" : {
        1 : 0,
        2 : 0,
        3 : 0,
        4 : 1,
        5 : 1,
        6 : 0,
        7 : 2,
        8 : 2,
        9 : 1,
    },
    "groupMode" : {
        1  : 0,
        2  : 1,
        3  : 0,
        4  : 0
    },
    "remoteVolumesSyncStatus" : {
        1: 0,
        2: 0,
        3: 0,
        4: 1,
        5: 1,
        6: 0,
        7: 0,
        8: 1,
        9: 2,
        10: 2,
        11: 1,
    },
    "roleReversed": False,
}


def get_hp3par_rcopyGroupStateEnum_desc( id ):
    result = 8

    if id in hp3par_rcopyGroupStateEnum:
        return hp3par_rcopyGroupStateEnum[id]

    return result

def get_hp3par_rcopyGroupModeEnum_desc( id ):
    result = 8

    if id in hp3par_rcopyGroupModeEnum:
        return hp3par_rcopyGroupModeEnum[id]

    return result

def get_hp3par_rcopyGroupVVStatusEnum_desc( id ):
    result = 8

    if id in hp3par_rcopyGroupVVStatusEnum:
        return hp3par_rcopyGroupVVStatusEnum[id]

    return result

def inventory_3par_remotecopygroups(parsed):
    inventory = []

    if "remotecopygroups" in parsed:
        for rcgroup in parsed["remotecopygroups"]["members"]:
            if "name" in rcgroup:
                inventory.append( ( rcgroup["name"], None ) )

    return inventory

def check_3par_remotecopygroups(item, params, parsed):
    state    = -1
    infotext = "not implemented - "
    perfdata = []

    if "remotecopygroups" in parsed:
        for rcgroup in parsed["remotecopygroups"]["members"]:
            if "name" in rcgroup and rcgroup["name"] == item:
                if rcgroup["role"] == 1:
                    infotext += "Primary"
                else:
                    infotext += "Secondary"

                if "targets" in rcgroup:
                    #Todo: handle multiple targets
                    #Todo: handle Policies
                    for rctarget in rcgroup["targets"]:
                        if "state" in rctarget:
                            infotext += " - %s" % ( get_hp3par_rcopyGroupStateEnum_desc( rctarget["state"] ) )
                            state = max( state, params["groupState"][rctarget["state"]] )
                        if "mode" in rctarget:
                            infotext += " - %s" % ( get_hp3par_rcopyGroupModeEnum_desc( rctarget["mode"] ) )
                            state = max( state, params["groupMode"][rctarget["mode"]] )
                        if rctarget["roleReversed"] is not params["roleReversed"]:
                            infotext += " - roleReversed is %s (!!)" % ( rctarget["roleReversed"] )
                            state = max( state, 2 )

                if "volumes" in rcgroup:
                    #Todo: handle multiple volumes
                    #Todo: handle last volumes
                    for rcgroupVolume in rcgroup["volumes"]:
                        if "remoteVolumes" in rcgroupVolume:
                            for remoteVolume in rcgroupVolume["remoteVolumes"]:
                                if "syncStatus" in remoteVolume:
                                    state = max( state, params["remoteVolumesSyncStatus"][remoteVolume["syncStatus"]])
                                    if params["remoteVolumesSyncStatus"][remoteVolume["syncStatus"]] != 0:
                                        infotext += " - Volumes are %s" % ( get_hp3par_rcopyGroupVVStatusEnum_desc( remoteVolume["syncStatus"] ) )
            # end name == item

    if state == -1:
        state = 3
    return ( state, infotext, perfdata )

check_info["3par.remotecopygroups"] = {
    'default_levels_variable':  "hp3par_rcopyGroupState_default_levels",
    'check_function':           check_3par_remotecopygroups,
    'inventory_function':       inventory_3par_remotecopygroups,
    'parse_function':           parse_3par,
    'service_description':      'RCG - %s',
    "has_perfdata"        :     True,
    'group':                    '3par_remotecopygroups',
}

##############################################
#
#   Physical Disks
# 
##############################################

hp3par_physicaldisks_default_levels = {}
factory_settings["hp3par_physicaldisks_default_levels"] = {
    "pdCountLevels":     ( 2, 1 ),
    "pdFailedLevels":    ( 1, 2 ),
    "lifeLeftPctLevels": ( 50, 30 ),
    "tempLevels":        ( 40, 50 ),
    "busyPctLevels":     ( 80, 90 ),
    "queueLengthLevels": ( 200, 350 ),
    "serviceTimeMSLevels": ( 10, 15 ),
    "serviceTimeMSLevels_SSD": ( 10, 15 ),
    "serviceTimeMSLevels_FC": ( 35, 45 ),
    "serviceTimeMSLevels_NL": ( 75, 100 ),
    "useMaxValues": False,
}

hp3par_pdType = {
    1: "FC",
    2: "NL",
    3: "SSD",
}

def get_hp3par_pdType_desc( id ):
    result = "Unknown"

    if id in hp3par_pdType:
        return hp3par_pdType[id]

    return result

def inventory_3par_physicaldisks(parsed):
    inventory = []

    typeCount = {
        1: 0,
        2: 0,
        3: 0,
    }

    if "sysrep_physicaldiskcapacity" in parsed:
        for pd in parsed["sysrep_physicaldiskcapacity"]["members"]:
            typeCount[pd["type"]] += 1            

    if typeCount[1] > 0:
        inventory.append( ( "PD Summary - FC", None ) )

    if typeCount[2] > 0:
        inventory.append( ( "PD Summary - NL", None ) )

    if typeCount[3] > 0:
        inventory.append( ( "PD Summary - SSD", None ) )

    return inventory

def check_3par_physicaldisks(item, params, parsed):
    state    = -1
    infotext = "not implemented"
    perfdata = []

    pd_count = 0
    pd_failed = 0

    lifeLeftPct_min = 100
    lifeLeftPct_max = 0
    lifeLeftPct_sum = 0

    temp_min = 255
    temp_max = 0
    temp_sum = 0

    busyPct_min = 100
    busyPct_max = 0
    busyPct_sum = 0

    queueLength_min = 9999
    queueLength_max = 0
    queueLength_sum = 0

    serviceTimeMS_total_min = 9999
    serviceTimeMS_total_max = 0
    serviceTimeMS_total_sum = 0

    if item == "PD Summary - FC":
        pd_type = 1
        stKey = "serviceTimeMSLevels_FC"
    if item == "PD Summary - NL":
        pd_type = 2
        stKey = "serviceTimeMSLevels_NL"
    if item == "PD Summary - SSD":
        pd_type = 3
        stKey = "serviceTimeMSLevels_SSD"

    # Todo: failed capacity

    if "sysrep_physicaldiskcapacity" in parsed:
        for pd in parsed["sysrep_physicaldiskcapacity"]["members"]:
            if pd["type"] != pd_type:
                continue
            pd_count += 1

        if pd_count <= params["pdCountLevels"][1]:
            state = max( state, 2 )
        elif pd_count <= params["pdCountLevels"][0]:
            state = max( state, 1 )
        else:
            state = max( state, 0 )

        perfdata.append( ( "pd_count", pd_count ) )
        infotext = "%s disks" % ( pd_count )

    if "sysrep_physicaldiskspacedata" in parsed:
        for pd in parsed["sysrep_physicaldiskspacedata"]["members"]:
            if pd["type"] != pd_type:
                continue

            if pd["spareChunklets"]["availClean"] <= 0 and pd["spareChunklets"]["usedOK"] <= 0:
                pd_failed += 1
                continue

            try:
                pdTempC = int(pd["temperatureC"])
            except ValueError:
                pdTempC = 0

            temp_min = min( temp_min, pdTempC )
            if temp_max < 255:
                temp_max = max( temp_max, pdTempC )
            temp_sum += pdTempC

            # only SSD shows wear gauge
            if pd["type"] != 3:
                continue

            lifeLeftPct_min = min( lifeLeftPct_min, pd["lifeLeftPct"] )
            lifeLeftPct_max = max( lifeLeftPct_max, pd["lifeLeftPct"] )
            lifeLeftPct_sum += pd["lifeLeftPct"]

        perfdata.append( ( "pd_failed", pd_failed ) )
        if pd_failed >= params["pdFailedLevels"][1]:
            state = max( state, 2 )
            infotext += " - %s failed(!!)" % ( pd_failed )
        elif pd_failed >= params["pdFailedLevels"][0]:
            state = max( state, 1 )
            infotext += " - %s failed(!)" % ( pd_failed )
        else:
            state = max( state, 0 )

        temp_avg = temp_sum / ( pd_count - pd_failed )

        if temp_max >= params["tempLevels"][1]:
            state = max( state, 2 )
            infotext += " - max. %s°C (!!)" % ( temp_max )
        elif temp_max >= params["tempLevels"][0]:
            state = max( state, 1 )
            infotext += " - max. %s°C (!)" % ( temp_max )
        else:
            state = max( state, 0 )

        perfdata.append( ( "temp_min", temp_min ) )
        perfdata.append( ( "temp_max", temp_max, params["tempLevels"][0], params["tempLevels"][1] ) )
        perfdata.append( ( "temp_avg", temp_avg ) )

        # only SSD shows wear gauge
        if pd_type == 3:
            lifeLeftPct_avg = lifeLeftPct_sum / pd_count

            if lifeLeftPct_min <= params["lifeLeftPctLevels"][1]:
                state = max( state, 2 )
                infotext += " - min. %s%% life left (!!)" % ( lifeLeftPct_min )
            elif lifeLeftPct_min <= params["lifeLeftPctLevels"][0]:
                state = max( state, 1 )
                infotext += " - min. %s%% life left (!)" % ( lifeLeftPct_min )
            else:
                state = max( state, 0 )
            
            perfdata.append( ( "lifeLeftPct_min", lifeLeftPct_min, params["lifeLeftPctLevels"][0], params["lifeLeftPctLevels"][1], 0, 100 ) )
            perfdata.append( ( "lifeLeftPct_max", lifeLeftPct_max, 0, 0, 0, 100 ) )
            perfdata.append( ( "lifeLeftPct_avg", lifeLeftPct_avg, 0, 0, 0, 100 ) )

    if "sysrep_physicaldiskstatistics" in parsed:
        for pd in parsed["sysrep_physicaldiskstatistics"]["members"]:
            if pd["type"] != pd_type:
                continue

            busyPct_min = min( busyPct_min, pd["busyPct"] )
            busyPct_max = max( busyPct_max, pd["busyPct"] )
            busyPct_sum += pd["busyPct"]

            queueLength_min = min( queueLength_min, pd["queueLength"] )
            queueLength_max = max( queueLength_max, pd["queueLength"] )
            queueLength_sum += pd["queueLength"]

            serviceTimeMS_total_min = min( serviceTimeMS_total_min, pd["serviceTimeMS"]["total"] )
            serviceTimeMS_total_max = max( serviceTimeMS_total_max, pd["serviceTimeMS"]["total"] )
            serviceTimeMS_total_sum += pd["serviceTimeMS"]["total"]

        busyPct_avg     = busyPct_sum / pd_count
        queueLength_avg = queueLength_sum / pd_count
        serviceTimeMS_total_avg = serviceTimeMS_total_sum / pd_count

        if params["useMaxValues"] == True:
            if busyPct_max >= params["busyPctLevels"][1]:
                state = max( state, 2 )
                infotext += " - max. %s%% busy (!!)" % ( busyPct_max )
            elif busyPct_max >= params["busyPctLevels"][0]:
                state = max( state, 1 )
                infotext += " - max. %s%% busy (!)" % ( busyPct_max )
            else:
                state = max( state, 0 )

            if queueLength_max >= params["queueLengthLevels"][1]:
                state = max( state, 2 )
                infotext += " - max. %s Queue (!!)" % ( queueLength_max )
            elif queueLength_max >= params["queueLengthLevels"][0]:
                state = max( state, 1 )
                infotext += " - max. %s Queue (!)" % ( queueLength_max )
            else:
                state = max( state, 0 )

            if serviceTimeMS_total_max >= params[stKey][1]:
                state = max( state, 2 )
                infotext += " - max. %0.0f ms (!!)(levels at %0.0f/%0.0f)" % ( serviceTimeMS_total_max, params[stKey][0], params[stKey][1] )
            elif serviceTimeMS_total_max >= params[stKey][0]:
                state = max( state, 1 )
                infotext += " - max. %0.0f ms (!)(levels at %0.0f/%0.0f)" % ( serviceTimeMS_total_max, params[stKey][0], params[stKey][1] )
            else:
                state = max( state, 0 )
                infotext += " - max. %0.0f ms ( Avg. %0.0f ms )" % ( serviceTimeMS_total_max, serviceTimeMS_total_avg )
        else:
            if busyPct_avg >= params["busyPctLevels"][1]:
                state = max( state, 2 )
                infotext += " - avg. %s%% busy (!!)" % ( busyPct_avg )
            elif busyPct_avg >= params["busyPctLevels"][0]:
                state = max( state, 1 )
                infotext += " - avg. %s%% busy (!)" % ( busyPct_avg )
            else:
                state = max( state, 0 )

            if queueLength_avg >= params["queueLengthLevels"][1]:
                state = max( state, 2 )
                infotext += " - avg. %s Queue (!!)" % ( queueLength_avg )
            elif queueLength_avg >= params["queueLengthLevels"][0]:
                state = max( state, 1 )
                infotext += " - avg. %s Queue (!)" % ( queueLength_avg )
            else:
                state = max( state, 0 )

            if serviceTimeMS_total_avg >= params[stKey][1]:
                state = max( state, 2 )
                infotext += " - avg. %0.0f ms (!!)(levels at %0.0f/%0.0f)" % ( serviceTimeMS_total_avg, params[stKey][0], params[stKey][1] )
            elif serviceTimeMS_total_avg >= params[stKey][0]:
                state = max( state, 1 )
                infotext += " - avg. %0.0f ms (!)(levels at %0.0f/%0.0f)" % ( serviceTimeMS_total_avg, params[stKey][0], params[stKey][1] )
            else:
                state = max( state, 0 )
                infotext += " - avg. %0.0f ms ( max. %0.0f ms )" % ( serviceTimeMS_total_avg, serviceTimeMS_total_max )

        perfdata.append( ( "busyPct_min", busyPct_min, 0, 0, 0, 100 ) )
        perfdata.append( ( "busyPct_max", busyPct_max, params["busyPctLevels"][0], params["busyPctLevels"][1], 0, 100 ) )
        perfdata.append( ( "busyPct_avg", busyPct_avg, 0, 0, 0, 100 ) )

        perfdata.append( ( "queueLength_min", queueLength_min, 0, 0, 0, 100 ) )
        perfdata.append( ( "queueLength_max", queueLength_max, params["queueLengthLevels"][0], params["queueLengthLevels"][1], 0, 100 ) )
        perfdata.append( ( "queueLength_avg", queueLength_avg, 0, 0, 0, 100 ) )

        perfdata.append( ( "serviceTimeMS_total_min", serviceTimeMS_total_min, 0, 0, 0, 100 ) )
        perfdata.append( ( "serviceTimeMS_total_max", serviceTimeMS_total_max, params[stKey][0], params[stKey][1], 0, 100 ) )
        perfdata.append( ( "serviceTimeMS_total_avg", serviceTimeMS_total_avg, 0, 0, 0, 100 ) )

    if state == -1:
        state = 3
    return ( state, infotext, perfdata )

check_info["3par.physicaldisks"] = {
    'default_levels_variable':  "hp3par_physicaldisks_default_levels",
    'check_function':           check_3par_physicaldisks,
    'inventory_function':       inventory_3par_physicaldisks,
    'parse_function':           parse_3par,
    'service_description':      '%s',
    "has_perfdata"        :     True,
    'group':                    '3par_physicaldisks',
}
