#!/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.

import sys, getopt, pprint, re, time
import traceback, pprint

try:
    from NaElement import *
    from NaServer import *
except Exception, e:
    sys.stderr.write("Unable to import the files NaServer.py/NaElement.py.\nThese files are "\
                     "provided by the NetApp Managability SDK and must be put into "\
                     "the sites folder ~/local/lib/python.\nDetailed error: %s\n" % e)
    sys.exit(1)


# Contains all objects to query
netapp_objects = {}
# Some objects might share data..
# E.g. the netapp_api_environ object requires information
# generated by the netapp_api_disk object
shared_data    = {}

netapp_objects["netapp_api_if"] = {
    "configs"          : { "net-ifconfig-get": [] },
    "counters"         : { "ifnet": ["recv_data", "send_data", "recv_mcasts",
                                    "send_mcasts", "recv_errors", "send_errors", "instance_name"] }
}

netapp_objects["netapp_api_cpu"] = {
    "counters"         :{ "system": ["cpu_busy", "num_processors"] }
}

netapp_objects["netapp_api_volumes"] = {
    "configs"          : { "volume-list-info" : ["name", "volume-info", "size-total", "size-available",
                                                 "volumes", "files-total", "files-used", "state"] },
    # Feel free to execute this construct in a python interpreter. It just creates a list of elements...
    "counters"         : { "volume": sum(map(lambda x: ["%s" % x, "nfs_%s" % x, "cifs_%s" % x, "san_%s" % x, "fcp_%s" % x, "iscsi_%s" % x],
                                        sum(map(lambda x: ["read_%s" % x, "write_%s" % x], ["data", "latency"]), [])), []) + [ "instance_name" ] }
}

netapp_objects["netapp_api_aggr"] = {
    "configs"          : { "aggr-list-info" :["aggr-info", "name", "size-total", "size-available", "aggregates"] }
}

netapp_objects["netapp_api_protocol"] = {
    "counters"         : {
                           "nfsv3": ["instance_name", "nfsv3_read_ops", "nfsv3_write_ops"],
                           "nfsv4": ["instance_name", "nfsv4_read_ops", "nfsv4_write_ops"],
                           "iscsi": ["instance_name", "iscsi_read_ops", "iscsi_write_ops"],
                           "cifs":  ["instance_name", "cifs_read_ops", "cifs_write_ops"],
                           "fcp":   ["instance_name", "fcp_read_ops", "fcp_write_data"],
#                           "system": [],
                          }
}

netapp_objects["netapp_api_version"] = {
    "configs"      : { "system-get-info"    : [],
                       "system-get-version" : [] }
}

netapp_objects["netapp_api_status"] = {
    "configs"      : { "diagnosis-status-get" : [] }
}

netapp_objects["netapp_api_cluster"] = {
    "configs"       : { "cf-status": [] },
}



def output_disks(results):
    bay_list   = results["configs"]["storage-shelf-bay-list-info"].child_get("shelf-bay-list")
    shelf_uids = {}
    for shelf in bay_list.children_get():
        shelf_uid = shelf.child_get_string("shelf-uid")
        if shelf_uid in shelf_uids:
            continue
        disk_uids = []
        for bay in shelf.child_get("port-list").children_get():
            disk_uid = bay.child_get_string("disk-uid")
            if disk_uid:
                disk_uids.append(disk_uid)
        shelf_uids[shelf_uid] = disk_uids

    shared_data["netapp_api_disk"] = {"shelf-uids": shelf_uids, "disks": {}}

    print "<<<netapp_api_disk:sep(9)>>>"
    print "[config_instance]\tdisk-list-info"

    disk_list = results["configs"]["disk-list-info"].child_get("disk-details")
    for disk in disk_list.children_get():
        disk_uid   = disk.child_get_string("disk-uid")
        raid_state = disk.child_get_string("raid-state")
        shared_data["netapp_api_disk"]["disks"][disk_uid] = raid_state

        print "disk-detail-info"
        print "%s\t%s" % ("disk-uid", disk_uid)
        print "%s\t%s" % ("raid-state", raid_state)
        for key in [ "bay", "used-space", "physical-space", "shelf", "serial-number",
                     "is-prefailed", "raid-type" ]:
            print "%s\t%s" % (key, disk.child_get_string(key))

    print "[config_instance]\tshelf-uids-of-disks"
    for shelf, disks in shelf_uids.items():
        print "shelf-uid-instance"
        print "shelf-uid\t%s" % shelf
        print "disks\t%s" % " ".join(disks)

netapp_objects["netapp_api_disk"] = {
    "configs"         : { "disk-list-info" : [],
                          "storage-shelf-bay-list-info" : [] },
    "output_function" : output_disks,
}

def output_environment(results):
    channel_list = results["configs"]["storage-shelf-environment-list-info"].child_get("shelf-environ-channel-list")

    # We need to iterate over each channel. There are dozens of sensors..
    def shelf_is_mine(shelf_uid):
        mine    = 0
        partner = 0
        if "netapp_api_disk" not in shared_data:
            return True

        # TODO: We had a case where shelf_uid is not contained in shared_data["netapp_api_disk"]["shelf-uids"].
        # I (mk) do not know why.
        if shelf_uid not in shared_data["netapp_api_disk"]["shelf-uids"]:
            return False

        for disk in shared_data["netapp_api_disk"]["shelf-uids"][shelf_uid]:
            state = shared_data["netapp_api_disk"]["disks"].get(disk)
            if state == "partner":
                partner += 1
            else:
                mine    += 1
        return mine > partner

    environ = {}
    for channel in channel_list.children_get():
        channel_name = channel.child_get_string("channel-name")
        shelf_list = channel.child_get("shelf-environ-shelf-list")

        if not shelf_list:
            continue

        for idx, child in enumerate(shelf_list.children_get()):
            current_shelf = child.child_get("ses-generic-info").child_get_string("ses-logical-id")
            # convert 0x50050cc002219406 to 50:05:0c:c0:02:21:94:06
            current_shelf = ":".join(current_shelf[i:i+2] for i in range(2, len(current_shelf), 2))

            for section_name, what, leaf, show_keys in [ ("netapp_api_psu",  "power-supply",    "power-control-status",
                                                           ["power-supply-is-not-installed",
                                                            "power-supply-element-number",
                                                            "power-supply-serial-no",
                                                            "power-supply-is-error"]),
                                                         ("netapp_api_temp", "temp-sensor",     None,
                                                           ["temp-sensor-is-ambient",
                                                            "temp-sensor-low-warning",
                                                            "temp-sensor-is-error",
                                                            "temp-sensor-low-critical",
                                                            "temp-sensor-hi-warning",
                                                            "temp-sensor-hi-critical",
                                                            "temp-sensor-element-no",
                                                            "temp-sensor-current-temperature"]),
                                                         ("netapp_api_fan",  "cooling-element", None,
                                                           ["cooling-element-is-not-installed",
                                                            "cooling-element-is-error",
                                                            "cooling-element-number"]) ]:
                environ.setdefault(section_name, {})
                if environ[section_name].get(current_shelf) != None:
                    continue
                environ[section_name][current_shelf] = {}
                if leaf and child.child_get_string(leaf):
                    environ[section_name][current_shelf][leaf] = [child.child_get_string(leaf)]

                what_list = child.child_get("%s-list" % what)
                for what_info in what_list.children_get():
                    for key in show_keys:
                        environ[section_name][current_shelf].setdefault(key, [])
                        node = what_info.child_get(key)
                        environ[section_name][current_shelf][key].append(node and node.element["content"] or "None")

    for section_name, shelfes in environ.items():
        print "<<<%s:sep(9)>>>" % section_name
        for shelf_id, keys in shelfes.items():
            print "%s\t%s" % (shelf_id, shelf_is_mine(shelf_id) and "shelf-owned\ttrue" or "shelf-owned\tfalse")
            for key, values in keys.items():
                print "%s\t%s\t%s" % (shelf_id, key, "\t".join(values))

netapp_objects["netapp_api_environ"] = {
    "configs"          : { "storage-shelf-environment-list-info": [] },
    "output_function"  : output_environment,
    "requires"         : "netapp_api_disk" # This object needs data from netapp_api_disk
}

def output_vfiler(results):
    vfilers = results["configs"]["vfiler-list-info"].child_get("vfilers").children_get()
    print "<<<netapp_api_vf_status:sep(9)>>>"
    for vfiler in vfilers:
        name = vfiler.child_get_string("name")
        cmd = NaElement("vfiler-get-status")
        cmd.child_add_string("vfiler", name)
        response = server.invoke_elem(cmd)
        print "%s\t%s" % (name, response.child_get_string("status"))

netapp_objects["netapp_api_vf_status"] = {
    "configs"                : { "vfiler-list-info": [] },
    "output_function"        : output_vfiler,
    "other_required_classes" : [ "vfiler-get-status" ]
}

netapp_objects["netapp_api_vf_stats"] = {
    "counters"         : { "vfiler": [] },
}

def usage():
    sys.stderr.write("""Check_MK NetApp Agent

USAGE: agent_netapp [OPTIONS] HOST

ARGUMENTS:
  HOST                          Host name or IP address of NetApp Filer

OPTIONS:
  -h, --help                    Show this help message and exit
  -u USER, --user USER          Username for NetApp login
  -s SECRET, --secret SECRET    Secret/Password for NetApp login
  -p, --port port               Alternative port number (default is 443 for the https connection)
  --debug                       Debug mode: let Python exceptions come through
  --xml,                        Dump xml messages into agent output
  -t, --timeout SECS            Set the network timeout to the NetApp filer to SECS seconds.
                                This is also used when connecting the agent (option -a).
                                Default is 60 seconds. Note: the timeout is not only
                                applied to the connection, but also to each individual
                                subquery.
""")
    sys.stderr.write("\nPlease ensure that the user account has access to the following classes:\n")
    required_classes = [ "perf-object-get-instances" ]
    for section_name, settings in netapp_objects.items():
        required_classes.extend(settings.get("configs", {}).keys())
        required_classes.extend(settings.get("other_required_classes", []))
    for entry in required_classes:
        sys.stderr.write(" - %s\n" % entry)

short_options = 'hu:s:t:'
long_options  = ['help', 'debug', 'user=', 'secret=', 'timeout=', 'xml']

# NetApp credentials
user   = None
secret = None

opt_timeout  = 60
opt_debug    = False
opt_dump_xml = False

try:
    opts, args = getopt.getopt(sys.argv[1:], short_options, long_options)
except getopt.GetoptError, err:
    sys.stderr.write("%s\n" % err)
    sys.exit(1)

for o,a in opts:
    if o in [ '--debug' ]:
        opt_debug = True
    elif o in [ '-u', '--user' ]:
        user = a
    elif o in [ '-s', '--secret' ]:
        secret = a
    elif o in [ '--xml']:
        opt_dump_xml = True
    elif o in [ '-t', '--timeout' ]:
        opt_timeout = int(a)
    elif o in [ '-h', '--help' ]:
        usage()
        sys.exit(0)

if len(args) == 1:
    host_address = args[0]
elif not args:
    sys.stderr.write("ERROR: No host given.\n")
    sys.exit(1)
else:
    sys.stderr.write("ERROR: Please specify exactly one host.\n")
    sys.exit(1)

try:
    server=NaServer(host_address,1,8)
    server.set_admin_user(user, secret)
    server.set_timeout(opt_timeout)
    server.set_transport_type("HTTPS")
    server.set_server_cert_verification(False)
    if opt_dump_xml:
        server.set_debug_style("NA_PRINT_DONT_PARSE")
except Exception, e:
    if opt_debug:
        raise
    error = "Cannot connect to NetApp Server. Maybe you provided wrong " \
            "credentials. Please check your connection settings and try " \
            "again."

def output_counters(element):
    result = []
    instances = element.child_get("instances")
    for instance_data in instances.children_get():
        result.append("---new_counter---")
        counters = instance_data.child_get("counters")
        for counter_entry in counters.children_get():
            name  = counter_entry.child_get_string("name")
            value = counter_entry.child_get_string("value")
            result.append("%s\t%s" % (name, value))
    return "\n".join(result)

def output_config(root_node, report_keys = []):
    def output_node(node, indent = 0):
        if report_keys and node.element["name"] not in report_keys:
            return ""
        answer = ""
        for entry in node.children_get():
            # TODO: remove indent sometime
            answer += output_node(entry, indent + 2)
        return "%s%s\t%s\n" % (" " * indent, node.element["name"], node.element["content"]) + answer

    response = []
    for entry in root_node.children_get():
        line = output_node(entry)
        if line:
            response.append(line)
    return "".join(response)

def generic_output(results, section_name):
    print "<<<%s:sep(9)>>>" % section_name
    for entry, data in results["configs"].items():
        print "[config_instance]\t%s" % entry
        print output_config(data, report_keys = netapp_objects[section_name]["configs"][entry])

    for entry, data in results["counters"].items():
        print "[counter_instance]\t%s" % entry
        print output_counters(data)

errors = []

try:
    # Basic sorting is sufficient right now...
    elements = []
    for entry in netapp_objects.items():
        if entry[1].get("requires"):
            elements.append(entry)
        else:
            elements.insert(0, entry)

    for section_name, settings in elements:
        time_start = time.time()
        results = {"configs": {}, "counters": {}}

        section_errors = []
        # configs and counters share a common key
        if settings.get("configs"):
            for name, keys in settings["configs"].items():
                response = server.invoke_elem(NaElement(name))

                if response.results_status() == "failed":
                    section_errors.append("In class %s: %s" % (name, response.results_reason()))
                else:
                    results["configs"][name] = response

        if settings.get("counters"):
            for counter, keys in settings.get("counters").items():
                cmd = NaElement("perf-object-get-instances")
                counters = NaElement("counters")
                if keys:
                    for key in keys:
                        counters.child_add_string("counter", key)
                    cmd.child_add(counters)
                cmd.child_add_string("objectname", counter)
                response = server.invoke_elem(cmd)

                if response.results_status() == "failed":
                    section_errors.append("In counter %s: %s" % (counter, response.results_reason()))
                else:
                    results["counters"][counter] = response

        if not section_errors:
            if settings.get("output_function"):
                settings["output_function"](results)
            else:
                generic_output(results, section_name)
        else:
            errors.extend(section_errors)

        if opt_debug:
            print "\nTime to execute section %s: %s" % (section_name, time.time() -  time_start)

    if errors and opt_debug:
        raise Exception("\n".join(errors))

except Exception, e:
    sys.stderr.write("Error(s) on processing the received data:\n%s" % e)
    sys.exit(1)

