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

# actual format
# <<<oracle_rman>>>
# TUX2|COMPLETED|2015-01-02_07:05:59|2015-01-02_07:05:59|DB_INCR|2|335|8485138
#
# old format
# <<<oracle_rman>>>
# TUX2 COMPLETED 2014-07-08_17:27:59 2014-07-08_17:29:35 DB_INCR 32
# TUX2 COMPLETED 2014-07-08_17:30:02 2014-07-08_17:30:06 ARCHIVELOG 121

# Columns: SID STATUS START END BACKUPTYPE BACKUPAGE

# Create DB_INCR_<Level> checks when parameter is True
# Set this to False for old behavior. This is required for the service
# discovery and can't be set as a inventory parameter.
# This will be removed in a later version of Check_MK. Don't use it for new installations!
inventory_oracle_rman_incremental_details = True


def parse_oracle_rman(info):
    parsed = {}
    error_sids = {}

    for line in info:
        line = line[1:]

        # Check for query errors
        err = oracle_handle_ora_errors(line)
        if err is False:
            continue  # ignore ancient agent outputs
        elif isinstance(err, tuple):
            sid = line[0]
            error_sids[sid] = err

        # we leave the llop with break when item is found except for DB_INCR_0
        # later we need to restore the values for DB_INCR_0 due to possivle
        #  overwrite with new line from info

        backupscn = -1
        item = ''

        if len(line) == 6:
            sid, status, _start, _end, backuptype, backupage = line
            item = "%s.%s" % (sid, backuptype)

            backupscn = int(-1)
            backuplevel = -1

        elif len(line) == 8:
            sid, status, _not_used_1, _end, backuptype, backuplevel, backupage, backupscn = line
            if backupscn == '':
                backupscn = int(-1)
            else:
                backupscn = int(backupscn)

            if backuptype == 'DB_INCR':

                if inventory_oracle_rman_incremental_details:
                    item = "%s.%s_%s" % (sid, backuptype, backuplevel)
                else:
                    # This is for really old plugins without an information for the backuplevel
                    item = "%s.%s" % (sid, backuptype)
            else:
                item = "%s.%s" % (sid, backuptype)

        else:
            continue

        try:
            backupage = int(backupage)

        except (ValueError, TypeError):
            backupage = None

        parsed.setdefault(
            item,
            {
                "sid": sid,
                "backuptype": backuptype,
                "backuplevel": backuplevel,
                "backupage": backupage,
                "status": status,
                "backupscn": backupscn,
                "used_incr_0": False,  # True when last incr0 is newer then incr1
            })

        # take the most current backupage in clustered environments
        if parsed[item]['backupage'] is None or parsed[item]['backupage'] > backupage:
            parsed[item].update({"backupage": backupage})

    # some tweaks in aprsed for change in behavior of oracle
    # correct backupage for INCR_1 when newer INCR_0 is existing
    for line in parsed:

        # search DB_INCR_1 in parsed
        if line.rsplit('.', 1)[1] == 'DB_INCR_1':

            # check backupage
            sid_level0 = "%s0" % (line[0:-1])

            if sid_level0 in parsed and parsed[sid_level0]['backupage'] < parsed[line]['backupage']:

                parsed[line].update({
                    "backupage": parsed[sid_level0]['backupage'],
                    "used_incr_0": True,
                })

    return parsed


def inventory_oracle_rman(parsed):
    inventory = []
    for line in parsed:

        sid = parsed[line]['sid']
        backuptype = parsed[line]['backuptype']
        backuplevel = parsed[line]['backuplevel']

        if backuptype in ('ARCHIVELOG', 'DB_FULL', 'DB_INCR', 'CONTROLFILE'):

            if inventory_oracle_rman_incremental_details and backuptype == 'DB_INCR':
                inventory.append(("%s.%s_%s" % (sid, backuptype, backuplevel), {}))
                continue

            else:
                inventory.append(("%s.%s" % (sid, backuptype), {}))

    return inventory


# Cannot use generic decorator @get_parsed_item_data because this check function
# needs special treatment
def check_oracle_rman(item, params, parsed):

    rman_backup = parsed.get(item)

    sid_level0 = ''

    if not rman_backup:

        # some versions of Oracle removes the last Level 1 after a new Level 0
        # => we have no Level 1 in agent output. level 1 is shown as level 0

        sid_level0 = "%s0" % (item[0:-1])

        if item[-1] == '1' and sid_level0 in parsed:

            # => INCR_1 in item and INCR_0 found
            # => Switch to INCR_0 + used_incr_0
            rman_backup = parsed.get(sid_level0)
            rman_backup.update({"used_incr_0": True})

        else:

            # In case of missing information we assume that the login into
            # the database has failed and we simply skip this check. It won't
            # switch to UNKNOWN, but will get stale.
            raise MKCounterWrapped("Login into database failed. Working on %s" % item)

    status = rman_backup['status']
    backupage = rman_backup['backupage']
    backupscn = rman_backup['backupscn']

    perfdata = []
    state = 2

    if status in ('COMPLETED', 'COMPLETED WITH WARNINGS'):

        if backupage is None:
            # backupage in agent was empty. That's only possible with really old agents.
            return 3, "Unknown backupage in check found. Please update agent."

        # backupage is time in minutes from agent!
        backupage = backupage * 60
        infotext = "Last backup %s ago" % get_age_human_readable(backupage)

        state = 0
        if "levels" in params:
            warn, crit = params.get("levels")
            if backupage >= crit:
                state = 2
            elif backupage >= warn:
                state = 1
            infotext += " (warn/crit at %s/%s)" % (get_age_human_readable(warn),
                                                   get_age_human_readable(crit))

            perfdata = [("age", backupage, warn, crit)]
        else:
            perfdata = [("age", backupage)]

        if backupscn > 0:
            infotext += ", incremental SCN %i" % backupscn

        if rman_backup['used_incr_0']:
            infotext += ', Last DB_INCR_0 used'
    else:
        infotext = "no COMPLETED backup found in last 14 days (very old plugin in use?)"

    return state, infotext, perfdata


check_info['oracle_rman'] = {
    "check_function": check_oracle_rman,
    "parse_function": parse_oracle_rman,
    "inventory_function": inventory_oracle_rman,
    "service_description": "ORA %s RMAN Backup",
    "has_perfdata": True,
    "node_info": True,
    "group": "oracle_rman",
    "includes": ["oracle.include", "db.include"]
}
