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

# <<<mssql_backup>>>
# MSSQL_SQLEXPRESS1 test123 1331207325

# <<<mssql_backup>>>
# MSSQL_SQL0x2 master 2016-07-08 20:20:27
# MSSQL_SQL0x2 model 2016-07-08 20:20:28
# MSSQL_SQL0x2 model 2016-07-12 09:09:42
# MSSQL_SQL0x2 model 2016-07-11 20:20:07
# MSSQL_SQL0x2 msdb 2016-07-08 20:20:43
# MSSQL_SQL0x2 msdb 2016-07-11 20:20:07

# <<<mssql_backup>>>
# MSSQL_SQL0x3 master 2016-07-08 20:20:27 D
# MSSQL_SQL0x3 model 2016-07-08 20:20:28 D
# MSSQL_SQL0x3 model 2016-07-12 09:09:42 L
# MSSQL_SQL0x3 model 2016-07-11 20:20:07 I
# MSSQL_SQL0x3 msdb 2016-07-08 20:20:43 D
# MSSQL_SQL0x3 msdb 2016-07-11 20:20:07 I

# <<<mssql_backup:sep(124)>>>
# MSSQL_SQL0x4|master|2016-07-08 20:20:27|D
# MSSQL_SQL0x4|model|2016-07-08 20:20:28|D
# MSSQL_SQL0x4|model|2016-07-12 09:09:42|L
# MSSQL_SQL0x4|model|2016-07-11 20:20:07|I
# MSSQL_SQL0x4|msdb|2016-07-08 20:20:43|D
# MSSQL_SQL0x4|msdb|2016-07-11 20:20:07|I

discovery_mssql_backup = []

factory_settings["mssql_backup_default_levels"] = {
    "database": (None, None),
    "database_diff": (None, None),
    "log": (None, None),
    "file_or_filegroup": (None, None),
    "file_diff": (None, None),
    "partial": (None, None),
    "partial_diff": (None, None),
    "unspecific": (None, None),
}


def parse_mssql_backup(info):
    def _parse_date_and_time(b_date, b_time):
        try:
            if b_time is None:
                return int(b_date)
            return time.mktime(time.strptime("%s %s" % (b_date, b_time), '%Y-%m-%d %H:%M:%S'))
        except ValueError:
            return None

    map_backup_types = {
        "D": "database",
        "I": "database diff",
        "L": "log",
        "F": "file or filegroup",
        "G": "file diff",
        "P": "partial",
        "Q": "partial diff",
        "-": "unspecific",
    }

    parsed = {}
    Backup = collections.namedtuple("Backup", ["timestamp", "type", "state"])
    for line in info:
        if len(line) <= 2:
            continue
        # handle one special case where spaces are in date/time:
        if len(line) == 4 and " " in line[2]:
            line = line[:2] + line[2].split(" ") + line[3:]

        # fill up with Nones:
        line += [None] * (6 - len(line))

        inst, tablespace, b_date, b_time, b_type, b_state = line
        timestamp = _parse_date_and_time(b_date, b_time)

        item = "%s %s" % (inst, tablespace)
        backup = Backup(timestamp, map_backup_types.get(b_type), b_state or "")
        parsed.setdefault(item, []).append(backup)

    return parsed


def inventory_mssql_backup(parsed):
    discovery_mode = _mssql_backup_discovery_mode()
    if discovery_mode != "summary":
        return
    for db_name in parsed:
        yield db_name, {}


def _mssql_backup_discovery_mode():
    rules = host_extra_conf(host_name(), discovery_mssql_backup)
    try:
        return rules[0]['mode']
    except (IndexError, KeyError):
        return "summary"  # default, comp. to older versions


def check_mssql_backup(item, params, parsed):
    data = parsed.get(item)
    if data is None:
        # Assume general connection problem to the database, which is reported
        # by the "X Instance" service and skip this check.
        raise MKCounterWrapped("Failed to connect to database")

    if not isinstance(params, dict):
        params = {"database": params}

    for backup in data:
        if backup.state == "no backup found":
            yield params.get("not_found", 1), "No backup found"
            continue
        if backup.state.startswith("ERROR: "):
            yield 2, backup.state[7:]
            continue
        if backup.type is None:
            backup_type_var = "database"
            perfkey = "seconds"
            backup_type_info = "[database]"
        else:
            backup_type_var = backup.type.strip().replace(" ", "_")
            perfkey = "backup_age_%s" % backup_type_var
            backup_type_info = "[%s]" % backup.type
        state, infotext, perfdata =\
            _check_mssql_backup(backup, params.get(backup_type_var, (None, None)), perfkey)
        yield state, "%s %s" % (backup_type_info, infotext), perfdata


def _check_mssql_backup(backup, levels, perfkey):
    state = 0
    age_warn, age_crit = levels
    sec_ago = time.time() - backup.timestamp
    if age_crit is not None and sec_ago >= age_crit:
        state = 2
    elif age_warn is not None and sec_ago >= age_warn:
        state = 1

    infotext = 'Last backup was at %s (%s ago)' % (time.strftime(
        '%Y-%m-%d %H:%M:%S', time.localtime(backup.timestamp)), get_age_human_readable(sec_ago))

    if state:
        infotext += " (warn/crit at %s/%s)" % (get_age_human_readable(age_warn),
                                               get_age_human_readable(age_crit))

    return state, infotext, [(perfkey, sec_ago, age_warn, age_crit)]


check_info['mssql_backup'] = {
    'parse_function': parse_mssql_backup,
    'check_function': check_mssql_backup,
    'inventory_function': inventory_mssql_backup,
    'service_description': 'MSSQL %s Backup',
    'has_perfdata': True,
    'group': 'mssql_backup',
    'default_levels_variable': 'mssql_backup_default_levels',
}

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


def _mssql_backup_per_type_item(db_name, backup):
    if backup.type is None:
        return "%s UNKNOWN" % db_name
    return "%s %s" % (db_name, backup.type.title())


def inventory_mssql_backup_per_type(parsed):
    discovery_mode = _mssql_backup_discovery_mode()
    if discovery_mode != "per_type":
        return
    for db_name, attrs in parsed.iteritems():
        for backup in attrs:
            yield _mssql_backup_per_type_item(db_name, backup), {}


def check_mssql_backup_per_type(item, params, parsed):
    for db_name, attrs in parsed.iteritems():
        for backup in attrs:
            if item == _mssql_backup_per_type_item(db_name, backup):
                return _check_mssql_backup(backup, params.get("levels", (None, None)), "backup_age")
    # Assume general connection problem to the database, which is reported
    # by the "X Instance" service and skip this check.
    raise MKCounterWrapped("Failed to connect to database")


check_info['mssql_backup.per_type'] = {
    'check_function': check_mssql_backup_per_type,
    'inventory_function': inventory_mssql_backup_per_type,
    'service_description': 'MSSQL %s Backup',
    'has_perfdata': True,
    'group': 'mssql_backup_per_type',
}
