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


# Output of old agent (< 1.1.10i2):
# AeLookupSvc        running  Application Experience Lookup Service
# Alerter            stopped  Alerter
# ALG                stopped  Application Layer Gateway Service
# AppMgmt            stopped  Application Management
# appmgr             running  Remote Server Manager

# Output of new agent (>= 1.1.10i2):
# Alerter stopped/disabled Warndienst
# ALG running/demand Gatewaydienst auf Anwendungsebene
# Apple_Mobile_Device running/auto Apple Mobile Device
# AppMgmt stopped/demand Anwendungsverwaltung
# AudioSrv running/auto Windows Audio
# BITS running/demand Intelligenter Hintergrund<FC>bertragungsdienst
# Bonjour_Service running/auto Dienst "Bonjour"

inventory_services = []

# Examples for inventory_services:
# inventory_services = [
#   "HirnTest",               # add service, if currently running
#   "TapiSrv running",        # the same
#   "TermService auto",       # add service, if start type is auto (regardless if running)
#   "BackupSrv running/auto", # add, if start type is auto and it's running
#   "~Backup.* running/auto",  # same, but add all services matching a regex
#   ( [ "termserver" ] , ALL_HOSTS, [ "HirnTest running", "Sppoller auto" ] ), # same with tags..
#   ( ALL_HOSTS, [ "!Backup.*", "FooBar auto" ] ),
# ]

# Implemented in 1.2.1i2:
# New rule-style (WATO compatible) notation:
#   [({'start_mode': 'demand', 'service': ['Netman']}, [], ['@all'], {'docu_url': ''})]
#
# <services> is list of regexes matching the service name
# <state> is the expected state to inventorize services of (running, stopped, ...)
# <start_mode> is the expected state to inventorize services of (auto, manual, ...)
#
# All above attributes can be set to None or not set to disable this filter option for the entry.
inventory_services_rules = []

def inventory_windows_services(info):
    # Handle single entries (type str)
    def add_matching_services(name, description, state, start_type, entry):
        if type(entry) == tuple:
            # New wato rule handling
            svc  = entry[0]
            statespec = entry[1:]

        elif ' ' in entry and len(entry.split()) == 2:
            svc, statespec = entry.split()

        else:
            svc = entry
            statespec = "running"

        # First match name or description (optional since rule based config option available)
        use_as_item = None
        if svc:
            if svc.startswith("~"):
                r = regex(svc[1:])
                if not r.match(name) and not r.match(description):
                    return []
            elif svc != name and svc != description:
                return []

        if type(statespec) == tuple:
            # New wato rule handling (always given as tuple of two)
            if (statespec[0] and statespec[0] != state) \
               or (statespec[1] and statespec[1] != start_type):
                return []

        else:
            for n in statespec.split("/"):
                if n not in [ state, start_type ]:
                    return []

        return [(name, {})]

    # Handle entries like ( [ "term" ], ALL_HOSTS, [ "FooBar auto", ".*TEST running" ] )
    def add_services_with_tags(name, description, state, start_type, entry):
        matching = []
        if len(entry) == 2:
            entry = ( [], ) + entry
        taglist, hostlist, svclist = entry
        if hosttags_match_taglist(tags_of_host(g_hostname), taglist):
            if in_extraconf_hostlist(hostlist, g_hostname):
                for svc in svclist:
                    matching += add_matching_services(name, description, state, start_type, svc)
        return matching

    # Filter WATO compatible rules by tags/hostlist
    rules = []
    for rule in inventory_services_rules:
        # 1. Get all rules matching the current host
        taglist, hostlist = rule[1:3]
        if not hosttags_match_taglist(tags_of_host(g_hostname), taglist) \
           or not in_extraconf_hostlist(hostlist, g_hostname):
            continue

        # 2. Now extract the list of service regexes
        value = rule[0]
        svcs  = value.get('services', [])
        state = value.get('state', None)
        start_mode  = value.get('start_mode', None)
        if svcs:
            for svc in svcs:
                rules.append(('~' + svc, state, start_mode))
        else:
            rules.append((None, state, start_mode))

    inventory = []
    for line in info:
        name = line[1]
        description = " ".join(line[3:])
        if '/' in line[2]:
            state, start_type = line[2].split('/')
        else:
            state = line[2]
            start_type = "unknown"

        #
        # Handle "old" inventory_services notation
        #
        for entry in inventory_services:
            if type(entry) == str:
                inventory += add_matching_services(name, description, state, start_type, entry)
            elif type(entry) == tuple:
                inventory += add_services_with_tags(name, description, state, start_type, entry)
            else:
                raise MKGeneralException("Invalid entry %r in inventory_services" % entry)

        #
        # New WATO compatible rule matching
        #

        for rule in rules:
            inventory += add_matching_services(name, description, state, start_type, rule)

    return inventory


# Format of parameters
# {
#    "states" : [ ( "running", "demand", 1 ),
#                  ( "stopped", None, 2 ) ],
#    "else" : 2,
# }

def check_windows_services(item, params, info):
    # Hack for old manually defined checks:
    if params == None:
        params = factory_settings["services_default_levels"]

    # A service may appear more than once (due to clusters).
    # First make a list of all matching entries with their
    # states
    found = []
    for line in info:
        # allow to match agains the internal name or agains the display name
        # of the service
        display_name = " ".join(line[3:])
        if item == line[1] or item == display_name \
           or line[1] in params['additional_servicenames'] \
           or display_name in params['additional_servicenames']:
            # newer agents also send start type as part of state,
            # e.g. running/auto
            if '/' in line[2]:
                state, start_type = line[2].split('/')
            else:
                state = line[2]
                start_type = "unknown"
            found.append((line[0], state, start_type, " ".join(line[3:])))

    if not found:
        return params["else"], "service not found"

    # We take the best found state (neccessary for clusters)
    best_state = None
    for running_on, state, start_type, desc in found:
        for t_state, t_start_type, mon_state in params["states"]:
            if (t_state == None or t_state == state) \
             and (t_start_type == None or t_start_type == start_type):
                this_state = mon_state
                break
        else:
            this_state = params["else"]

        if best_state == None or this_state < best_state:
            best_state = this_state
            best_info = state, start_type
            best_running_on = running_on
    infotext = desc + ": %s (start type is %s)" % best_info
    if best_running_on and best_state != 2: #if best state ist critical, there should no message "running on"
        infotext += " (running on: %s)" % best_running_on

    return (best_state, infotext)


factory_settings["services_default_levels"] = {
    "states"                    : [ ( "running", None, 0 ) ],
    "else"                      : 2,
    "additional_servicenames"   : [],
}

check_info['services'] = {
    "check_function"          : check_windows_services,
    "inventory_function"      : inventory_windows_services,
    "service_description"     : "Service %s",
    "has_perfdata"            : False,
    "node_info"               : True,
    "group"                   : "services",
    "default_levels_variable" : "services_default_levels",
}

factory_settings["services_summary_default_levels"] = {
        "ignored": [],
        "state_if_stopped": 0
}

def inventory_services_summary(info):
    if info[0]:
        return [ (None, "services_summary_default_levels") ]

def check_services_summary(item, params, info):
    blacklist = params.get("ignored", () )
    stoplist = []
    num_blacklist = 0
    num_auto = 0
    for line in info:
        # newer agents also send start type as part of state,
        # e.g. running/auto
        if '/' in line[2]:
            startstop, auto = line[2].split('/')
        else:
            startstop = line[2]
            auto      = "unknown"
        srv_name = line[1]
        if auto == "auto":
            num_auto += 1
            if startstop == "stopped":
                match = False
                for srv in blacklist:
                    if  re.match(srv,srv_name):
                        match = True
                if match == False:
                    stoplist.append(srv_name)
                else:
                    num_blacklist += 1

    num_stoplist = len(stoplist)
    num_srv = len(info)
    length_runlist = len(info) - len(stoplist) - num_blacklist

    if len(stoplist) > 0:
        stopped_srvs = " ("+", ".join(stoplist)+")"
        state = params.get("state_if_stopped")
        if state == 1:
            sym = "(!)"
        elif state == 2:
            sym="(!!)"
        else:
            sym = ""
    else:
        stopped_srvs = ""
        state = 0
        sym = ""


    infotext = "%d services, %d services in autostart - of which %d services are stopped%s%s, %d services stopped but ignored" % \
        ( num_srv, num_auto, num_stoplist, sym, stopped_srvs,  num_blacklist )

    return state, infotext

check_info['services.summary'] = {
    "check_function"          : check_services_summary,
    "inventory_function"      : inventory_services_summary,
    "default_levels_variable" : "services_summary_default_levels",
    "service_description"     : "Services Summary",
    "node_info"               : True,
    "group"                   : "services_summary",
}
