#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2014             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# ails.  You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.

# We use the following OIDs:
# PowerNet-MIB::upsBasicBatteryStatus.0    .1.3.6.1.4.1.318.1.1.1.2.1.1.0
# PowerNet-MIB::upsBasicOutputStatus.0     .1.3.6.1.4.1.318.1.1.1.4.1.1.0
# PowerNet-MIB::upsAdvBatteryCapacity.0    .1.3.6.1.4.1.318.1.1.1.2.2.1.0
# PowerNet-MIB::upsAdvBatteryTemperature.0 .1.3.6.1.4.1.318.1.1.1.2.2.2.0
# PowerNet-MIB::upsAdvBatteryReplaceIndicator.0 .1.3.6.1.4.1.318.1.1.1.2.2.4.0
# PowerNet-MIB::upsAdvBatteryNumOfBadBattPacks.0 .1.3.6.1.4.1.318.1.1.1.2.2.6.0
# PowerNet-MIB::upsAdvBatteryCurrent.0     .1.3.6.1.4.1.318.1.1.1.2.2.9.0
# PowerNet-MIB::upsAdvOutputVoltage.0      .1.3.6.1.4.1.318.1.1.1.4.2.1.0
# PowerNet-MIB::upsAdvOutputCurrent.0      .1.3.6.1.4.1.318.1.1.1.4.2.4.0
# PowerNet-MIB::upsAdvBatteryRunTimeRemaining.0      .1.3.6.1.4.1.318.1.1.1.2.2.3.0
# PowerNet-MIB::upsAdvTestCalibrationResults   .1.3.6.1.4.1.318.1.1.1.7.2.6

# upsBasicBatteryStatus: unknown(1), batteryNormal(2), batteryLow(3)
# upsBasicOutputStatus: unknown(1),  onLine(2), onBattery(3), onSmartBoost(4),
#   timedSleeping(5), softwareBypass(6), off(7), rebooting(8), switchedBypass(9),
#   hardwareFailureBypass(10), sleepingUntilPowerReturn(11), onSmartTrim(12)
# upsAdvTestCalibrationResults: ok(1), invalidTest(2), calibrationInProgress(3)

# PowerNet-MIB::upsAdvTestDiagnosticsResults   .1.3.6.1.4.1.318.1.1.1.7.2.3
# upsAdvTestDiagnosticsResults OBJECT-TYPE
#         SYNTAX INTEGER {
#                 ok(1),
#                 failed(2),
#                 invalidTest(3),
#                 testInProgress(4)
#         }
#         ACCESS read-only
#         STATUS mandatory
#         DESCRIPTION
#                 "The results of the last UPS diagnostics test performed."
#         ::= { upsAdvTest 3 }

# PowerNet-MIB::upsAdvTestLastDiagnosticsDate  .1.3.6.1.4.1.318.1.1.1.7.2.4
# upsAdvTestLastDiagnosticsDate OBJECT-TYPE
#         SYNTAX DisplayString
#         ACCESS read-only
#         STATUS mandatory
#         DESCRIPTION
#                 "The date the last UPS diagnostics test was performed in
#                  mm/dd/yy format."
#         ::= { upsAdvTest 4 }
#
# AdvOutputLoad     1.3.6.1.4.1.318.1.1.1.4.2.3.0
# Returns     The current UPS load expressed in percent of rated capacity.
# Monitoring Condition    Pass if less than 50

# old format:
# apc_default_levels = ( 95, 40, 1, 220 )
# Temperature default now 60C: regadring to a apc technician a temperature up tp 70C is possible
factory_settings["apc_default_levels"] = {
        "levels": ( 95, 60, 1, 220 )
}

def check_apc(item, params, info):
    BasicBatteryStatus, BasicOutputStatus, AdvBatteryCapacity, \
    AdvBatteryTemperature, AdvBatteryReplaceIndicator, AdvBatteryNumOfBadBattPacks, \
    AdvBatteryCurrent, AdvInputVoltage, AdvOutputVoltage, \
    AdvOutputCurrent, AdvBatteryRunTimeRemaining, AdvTestCalibrationResults, AdvOutputLoad \
                                                = [ saveint(x) for x in info[0][:13] ]

    last_diag_date = info[0][-1]
    RunTimeRemaining = AdvBatteryRunTimeRemaining / 100

    alt_crit_capacity = None
    # convert old format tuple to dict
    if type(params) is tuple:
        params = { "levels": params }

    # new format with up to 6 params in dict
    crit_capacity, crit_sys_temp, crit_batt_curr, crit_voltage = params['levels']
    if params.get("post_calibration_levels"):
        if last_diag_date != 'Unknown' and len(last_diag_date) in [8, 10]:
            year_format = len(last_diag_date) == 8 and '%y' or '%Y'
            last_ts = time.mktime(time.strptime(last_diag_date, '%m/%d/'+year_format))
            diff_sec = time.time() - last_ts

            allowed_delay_sec = 86400 + params['post_calibration_levels']['additional_time_span']
            alt_crit_capacity = params['post_calibration_levels']['altcapacity']

    single_states = []

    # 1. Check battery status
    status_text = { 1:"unknown", 2:"normal", 3:"low" }
    infotxt = "Battery status: %s" % (status_text.get(BasicBatteryStatus))
    if BasicBatteryStatus != 2:
        state = 2
        infotxt += "(!!)"
        single_states.append( (state, infotxt, None) )
    else:
        state = 0

    # 2. Check battery replacement status
    if AdvBatteryReplaceIndicator == 2:
        if AdvBatteryNumOfBadBattPacks == 1:
            infotxt = "one battery needs replacement(!)"
            state = 1
        elif AdvBatteryNumOfBadBattPacks > 1:
            infotxt = "%i batteries need replacement(!!)" % AdvBatteryNumOfBadBattPacks
            state = 2
        single_states.append( (state, infotxt, None) )
    elif state == 0: # if normal only print when replacement status is also OK, to save on service output text
        single_states.append( (state, infotxt, None) )

    # 3. Check basic output status
    status_text = { 1:"unknown", 2:"online", 3:"on battery", 4:"on smart boost", 5:"timed sleeping",
                    6:"software bypass", 7:"off", 8:"rebooting", 9:"switched bypass",
                   10:"hardware failure bypass", 11:"sleeping until power return",
                   12:"on smart trim" }
    calib_text = { 1:"", 2:" (calibration invalid)", 3:" (calibration in progress)" }
    infotxt = "output status: %s%s" % (status_text.get(BasicOutputStatus), calib_text.get(AdvTestCalibrationResults))
    # during calibration test is OK
    if BasicOutputStatus not in [2, 4, 12] and AdvTestCalibrationResults != 3:
        state = 2
        infotxt += "(!!)"
    else:
        state = 0
    single_states.append( (state, infotxt, None) )

    # 4. Check battery capacity
    state = 0
    infotxt = "capacity %d%% (crit at " % AdvBatteryCapacity

    if alt_crit_capacity != None and diff_sec < allowed_delay_sec:
        infotxt += "%d%% in delay after calib.)" % alt_crit_capacity
        if AdvBatteryCapacity <= alt_crit_capacity:
            state = 2
            infotxt += "(!!)"
    else:
        infotxt += "%d%%)" % crit_capacity
        if AdvBatteryCapacity <= crit_capacity:
            state = 2
            infotxt += "(!!)"

    single_states.append( (state, infotxt, ("capacity", AdvBatteryCapacity, "", crit_capacity, 0, 100)) )

    # 5. Check System temperature
    # The Name AdvBatteryTemperature is a wrong naming in the vendor mib.
    infotxt = "sys. temp. %d °C" % AdvBatteryTemperature
    if AdvBatteryTemperature >= crit_sys_temp:
        state = 2
        infotxt += "(!!)"
    else:
        state = 0
    single_states.append( (state, infotxt, ("systemp", AdvBatteryTemperature, "", crit_sys_temp) ) )

    # 6. Check battery current
    infotxt = "bat. curr. %d A" % AdvBatteryCurrent
    if AdvBatteryCurrent >= crit_batt_curr:
        state = 2
        infotxt += "(!!)"
    else:
        state = 0
    single_states.append( (state, infotxt, ("batcurr", AdvBatteryCurrent, "", crit_batt_curr, 0) ) )

    # 6a. Simply show input voltage (no performance data)
    single_states.append( (0, "input voltage %d V" % AdvInputVoltage, None) )

    # 7. Check output voltage
    infotxt = "output voltage %d V" % AdvOutputVoltage
    if AdvOutputVoltage <= crit_voltage:
        state = 2
        infotxt += "(!!)"
    else:
        state = 0
    single_states.append( (state, infotxt, ("voltage", AdvOutputVoltage, "", crit_voltage, 0) ) )

    # 8. Simply add output current as perfdata
    single_states.append( (0, "output current %d A" % AdvOutputCurrent, ("current", AdvOutputCurrent)) )

    # 9. run time remaining
    # RunTimeRemaining formatiert == "26:00.00"
    hrs = int(RunTimeRemaining) / 3600
    mins, secs = divmod(int(RunTimeRemaining) % 3600, 60)
    single_states.append( (0, "run time remaining: %02d:%02d:%02d" % (hrs, mins, secs),
                            ("runtime", RunTimeRemaining/60)) )

    # 10. Adv Output load (load in percent)
    load_state = 0
    loadwarn, loadcrit = None, None
    label = ""
    if params.get('output_load'):
        loadwarn, loadcrit = params['output_load']
        if AdvOutputLoad >= loadcrit:
            load_state = 2
            label = "(!!)"
        elif AdvOutputLoad >= loadwarn:
            load_state = 1
            label = "(!)"
    single_states.append( ( load_state, "current output load %d%%%s" % (AdvOutputLoad, label), \
                        ( "OutputLoad", AdvOutputLoad, loadwarn, loadcrit ) ) )

    # create summary state
    worst_state = max([x[0] for x in single_states])
    info_text = ", ".join([x[1] for x in single_states])

    return (worst_state, info_text, [x[2] for x in single_states if x[2] != None])

def inventory_apc(info):
    if len(info) > 0:
        return [(None, "apc_default_levels")]


check_info['apc_symmetra'] = {
  "inventory_function"      : inventory_apc,
  "check_function"          : check_apc,
  "service_description"     : "APC Symmetra status",
  "has_perfdata"            : True,
  "group"                   : "apc_symentra",
  "default_levels_variable" : "apc_default_levels",
  "snmp_scan_function"      : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.318.1.3"),
  "snmp_info"               : (".1.3.6.1.4.1.318.1.1.1",
                          [
                            "2.1.1.0", # BasicBatteryStatus,
                            "4.1.1.0", # BasicOutputStatus,
                            "2.2.1.0", # AdvBatteryCapacity,
                            "2.2.2.0", # AdvBatteryTemperature,
                            "2.2.4.0", # AdvBatteryReplaceIndicator,
                            "2.2.6.0", # AdvBatteryNumOfBadBattPacks,
                            "2.2.9.0", # AdvBatteryCurrent,
                            "3.2.1.0", # AdvInputVoltage,
                            "4.2.1.0", # AdvOutputVoltage,
                            "4.2.4.0", # AdvOutputCurrent,
                            "2.2.3.0", # AdvBatteryRunTimeRemaining,
                            "7.2.6.0", # AdvTestCalibrationResults
                            "4.2.3.0", # AdvOutputLoad
                            "7.2.4.0", # LastDiagnosticsDate
                        ] ),
}

