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

# In cooperation with Thorsten Bruhns

# <<<oracle_instance:sep(124)>>>
# TUX2|12.1.0.1.0|OPEN|ALLOWED|STARTED|6735|1297771692|ARCHIVELOG|PRIMARY|NO|TUX2
# TUX5|12.1.0.1.1|MOUNTED|ALLOWED|STARTED|82883|1297771692|NOARCHIVELOG|PRIMARY|NO|0|TUX5

# <<<oracle_instance:sep(124)>>>$
# +ASM|FAILURE|ORA-99999 tnsping failed for +ASM $
# ERROR:$
# ORA-28002: the password will expire within 1 days$

factory_settings["oracle_instance_defaults"] = {
    "logins": 2,
    "noforcelogging": 1,
    "noarchivelog": 1,
    "primarynotopen": 2,
}


def inventory_oracle_instance(info):

    inv_list = []

    for line in info:

        # Skip ORA- error messages from broken old oracle agent
        # <<<oracle_instance:sep(124)>>>
        # ORA-99999 tnsping failed for +ASM1
        if not (line[0].startswith('ORA-') and line[0][4].isdigit() and len(line[0]) < 16):

            # possible multitenant entry?
            if len(line) > 12:

                # every pdb has a con_id != 0
                # Multitenant use DB_NAME.PDB_NAME as Service
                if line[12] == 'TRUE' and line[13] != '0':
                    inv_list.append((("%s.%s" % (line[0], line[14])), {}))
                    continue

            inv_list.append((line[0], {}))

    return inv_list


def _transform_oracle_instance_params(p):
    if "ignore_noarchivelog" in p:
        if p["ignore_noarchivelog"]:
            p["noarchivelog"] = 0
        del p["ignore_noarchivelog"]
    return p


def check_oracle_instance(item, params, info):
    params = _transform_oracle_instance_params(params)

    def state_marker(state, infotext, param, column, data):
        value = params.get(param)
        if value is not None and column.lower() == data.lower():
            state = max(state, value)
            if value == 1:
                infotext += '(!)'
            elif value == 2:
                infotext += '(!!)'
        return state, infotext

    state = 0

    for line in info:

        itemname = line[0]
        pdb = False
        pluggable = "false"

        if len(line) > 12:
            # Multitenant!
            if line[12] == 'TRUE' and line[13] != '0':
                itemname = ("%s.%s" % (line[0], line[14]))
                pdb = True

        if itemname == item:
            # In case of a general error (e.g. authentication failed), the second
            # column contains the word "FAILURE"
            if line[1] == 'FAILURE':
                return 2, " ".join(line[2:])

            state = 0

            # Be compatible to old oracle agent plugin output
            if len(line) == 6:
                sid, version, openmode, logins, _unused, _unused2 = line

                infotext = 'Status %s, Version %s, Logins %s' % (openmode, version, logins.lower())
                state, infotext = state_marker(state, infotext, 'logins', logins, 'RESTRICTED')
                return state, infotext

            elif len(line) == 11:
                sid, version, openmode, logins, archiver, up_seconds, _dbid, \
                    log_mode, database_role, force_logging, name = line

            elif len(line) == 12:
                sid, version, openmode, logins, archiver, up_seconds, _dbid, \
                    log_mode, database_role, force_logging, name, _dbcreated = line

            elif len(line) == 22:
                sid, version, openmode, logins, archiver, up_seconds, _dbid, \
                    log_mode, database_role, force_logging, name, _dbcreated, pluggable, \
                    _con_id, pname, _pdbid, popenmode, prestricted, ptotal_size, _precovery_status, \
                    pup_seconds, _pblock_size = line

            else:
                return 2, "Database not running, login failed or unvalid data from agent"

            if pdb:
                if prestricted.lower() == 'no':
                    logins = 'RESTRICTED'
                else:
                    logins = 'ALLOWED'

                openmode = popenmode
                up_seconds = pup_seconds

                infotext = "PDB Name %s.%s, Status %s" % \
                           (name, pname, popenmode)
            else:
                if pluggable.lower() == 'true':
                    infotext = "CDB Name %s, Status %s" % \
                               (name, openmode)
                else:
                    infotext = "Database Name %s, Status %s" % \
                               (name, openmode)

            # Check state for PRIMARY Database. Normaly there are always OPEN
            if database_role == 'PRIMARY' and openmode not in ('OPEN', 'READ ONLY', 'READ WRITE'):
                state = params.get('primarynotopen')
                if state == 1:
                    infotext += '(!)'
                elif state == 2:
                    infotext += '(!!)'
                elif state == 0:
                    infotext += ' (allowed by rule)'

            if not pdb:
                infotext += ", Role %s, Version %s" % (database_role, version)

            # ORACLE is sick and cannot handle timezone changes >:-P
            up_seconds = max(0, int(up_seconds))
            infotext += ", Up since %s (%s)" % (
                       time.strftime("%F %T", time.localtime(time.time() - up_seconds)), \
                       get_age_human_readable(up_seconds))

            if params.get('uptime_min'):
                warn, crit = params.get('uptime_min')
                warn = int(warn)
                crit = int(crit)

                infotext += ' (warn/crit at %s/%s)' % (get_age_human_readable(warn),
                                                       get_age_human_readable(crit))
                if up_seconds == -1:
                    # PDB in mounted state has no uptime information
                    state = 0
                elif up_seconds <= crit:
                    state = 2
                    infotext += '(!!) not long enough up'
                elif up_seconds <= warn:
                    state = max(state, 1)
                    infotext += '(!) not long enough up'

            # ASM has no login and archivelog check
            if database_role != 'ASM':

                # logins are only possible when the database is open
                if openmode == 'OPEN':
                    infotext += ', Logins %s' % (logins.lower())
                    state, infotext = state_marker(state, infotext, 'logins', logins, 'RESTRICTED')

                # the new internal database _MGMTDB from 12.1.0.2 is always in NOARCHIVELOG mode
                if name != '_MGMTDB' and sid != '-MGMTDB' and not pdb:
                    infotext += ', Log Mode %s' % (log_mode.lower())
                    state, infotext = state_marker(state, infotext, 'archivelog', log_mode,
                                                   'ARCHIVELOG')
                    state, infotext = state_marker(state, infotext, 'noarchivelog', log_mode,
                                                   'NOARCHIVELOG')

                    # archivelog is only valid in non pdb
                    # force logging is only usable when archivelog is enabled
                    if log_mode == 'ARCHIVELOG':
                        if archiver != 'STARTED':
                            infotext += '. Archiver %s(!!)' % (archiver.lower())
                            state = 2

                        infotext += ', Force Logging %s' % (force_logging.lower())
                        state, infotext = state_marker(state, infotext, 'forcelogging',
                                                       force_logging, 'YES')
                        state, infotext = state_marker(state, infotext, 'noforcelogging',
                                                       force_logging, 'NO')

            perfdata = [('uptime', up_seconds)]

            if pdb:
                infotext += ', PDB Size %s' % get_bytes_human_readable(int(ptotal_size))
                perfdata.append(('fs_size', int(ptotal_size)))

            return state, infotext, perfdata

    if '.' in item:
        # no error from PDB when CDB is down
        raise MKCounterWrapped("CDB possible down. No results for PDB availible!")
    else:
        return 2, "Database not running or login failed"


check_info['oracle_instance'] = {
    "check_function": check_oracle_instance,
    "inventory_function": inventory_oracle_instance,
    "service_description": "ORA %s Instance",
    "default_levels_variable": "oracle_instance_defaults",
    "group": "oracle_instance",
    "has_perfdata": True,
}
