diff options
Diffstat (limited to 'roles')
| -rw-r--r-- | roles/lib_zabbix/library/zbx_action.py | 538 | 
1 files changed, 538 insertions, 0 deletions
| diff --git a/roles/lib_zabbix/library/zbx_action.py b/roles/lib_zabbix/library/zbx_action.py new file mode 100644 index 000000000..d64cebae1 --- /dev/null +++ b/roles/lib_zabbix/library/zbx_action.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python +''' + Ansible module for zabbix actions +''' +# vim: expandtab:tabstop=4:shiftwidth=4 +# +#   Zabbix action ansible module +# +# +#   Copyright 2015 Red Hat Inc. +# +#   Licensed under the Apache License, Version 2.0 (the "License"); +#   you may not use this file except in compliance with the License. +#   You may obtain a copy of the License at +# +#       http://www.apache.org/licenses/LICENSE-2.0 +# +#   Unless required by applicable law or agreed to in writing, software +#   distributed under the License is distributed on an "AS IS" BASIS, +#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#   See the License for the specific language governing permissions and +#   limitations under the License. +# + +# This is in place because each module looks similar to each other. +# These need duplicate code as their behavior is very similar +# but different for each zabbix class. +# pylint: disable=duplicate-code + +# pylint: disable=import-error +from openshift_tools.monitoring.zbxapi import ZabbixAPI, ZabbixConnection, ZabbixAPIError + +def exists(content, key='result'): +    ''' Check if key exists in content or the size of content[key] > 0 +    ''' +    if not content.has_key(key): +        return False + +    if not content[key]: +        return False + +    return True + +def conditions_equal(zab_conditions, user_conditions): +    '''Compare two lists of conditions''' +    c_type = 'conditiontype' +    _op = 'operator' +    val = 'value' +    if len(user_conditions) != len(zab_conditions): +        return False + +    for zab_cond, user_cond in zip(zab_conditions, user_conditions): +        if zab_cond[c_type] != str(user_cond[c_type]) or zab_cond[_op] != str(user_cond[_op]) or \ +           zab_cond[val] != str(user_cond[val]): +            return False + +    return True + +def filter_differences(zabbix_filters, user_filters): +    '''Determine the differences from user and zabbix for operations''' +    rval = {} +    for key, val in user_filters.items(): + +        if key == 'conditions': +            if not conditions_equal(zabbix_filters[key], val): +                rval[key] = val + +        elif zabbix_filters[key] != str(val): +            rval[key] = val + +    return rval + +# This logic is quite complex.  We are comparing two lists of dictionaries. +# The outer for-loops allow us to descend down into both lists at the same time +# and then walk over the key,val pairs of the incoming user dict's changes +# or updates.  The if-statements are looking at different sub-object types and +# comparing them.  The other suggestion on how to write this is to write a recursive +# compare function but for the time constraints and for complexity I decided to go +# this route. +# pylint: disable=too-many-branches +def operation_differences(zabbix_ops, user_ops): +    '''Determine the differences from user and zabbix for operations''' + +    # if they don't match, take the user options +    if len(zabbix_ops) != len(user_ops): +        return user_ops + +    rval = {} +    for zab, user in zip(zabbix_ops, user_ops): +        for key, val in user.items(): +            if key == 'opconditions': +                for z_cond, u_cond in zip(zab[key], user[key]): +                    if not all([str(u_cond[op_key]) == z_cond[op_key] for op_key in \ +                                ['conditiontype', 'operator', 'value']]): +                        rval[key] = val +                        break +            elif key == 'opmessage': +                # Verify each passed param matches +                for op_msg_key, op_msg_val in val.items(): +                    if zab[key][op_msg_key] != str(op_msg_val): +                        rval[key] = val +                        break + +            elif key == 'opmessage_grp': +                zab_grp_ids = set([ugrp['usrgrpid'] for ugrp in zab[key]]) +                usr_grp_ids = set([ugrp['usrgrpid'] for ugrp in val]) +                if usr_grp_ids != zab_grp_ids: +                    rval[key] = val + +            elif key == 'opmessage_usr': +                zab_usr_ids = set([usr['userid'] for usr in zab[key]]) +                usr_ids = set([usr['userid'] for usr in val]) +                if usr_ids != zab_usr_ids: +                    rval[key] = val + +            elif zab[key] != str(val): +                rval[key] = val +    return rval + +def get_users(zapi, users): +    '''get the mediatype id from the mediatype name''' +    rval_users = [] + +    for user in users: +        content = zapi.get_content('user', +                                   'get', +                                   {'filter': {'alias': user}}) +        rval_users.append({'userid': content['result'][0]['userid']}) + +    return rval_users + +def get_user_groups(zapi, groups): +    '''get the mediatype id from the mediatype name''' +    user_groups = [] + +    content = zapi.get_content('usergroup', +                               'get', +                               {'search': {'name': groups}}) + +    for usr_grp in content['result']: +        user_groups.append({'usrgrpid': usr_grp['usrgrpid']}) + +    return user_groups + +def get_mediatype_id_by_name(zapi, m_name): +    '''get the mediatype id from the mediatype name''' +    content = zapi.get_content('mediatype', +                               'get', +                               {'filter': {'description': m_name}}) + +    return content['result'][0]['mediatypeid'] + +def get_priority(priority): +    ''' determine priority +    ''' +    prior = 0 +    if 'info' in priority: +        prior = 1 +    elif 'warn' in priority: +        prior = 2 +    elif 'avg' == priority or 'ave' in priority: +        prior = 3 +    elif 'high' in priority: +        prior = 4 +    elif 'dis' in priority: +        prior = 5 + +    return prior + +def get_event_source(from_src): +    '''Translate even str into value''' +    choices = ['trigger', 'discovery', 'auto', 'internal'] +    rval = 0 +    try: +        rval = choices.index(from_src) +    except ValueError as _: +        ZabbixAPIError('Value not found for event source [%s]' % from_src) + +    return rval + +def get_status(inc_status): +    '''determine status for action''' +    rval = 1 +    if inc_status == 'enabled': +        rval = 0 + +    return rval + +def get_condition_operator(inc_operator): +    ''' determine the condition operator''' +    vals = {'=': 0, +            '<>': 1, +            'like': 2, +            'not like': 3, +            'in': 4, +            '>=': 5, +            '<=': 6, +            'not in': 7, +           } + +    return vals[inc_operator] + +def get_host_id_by_name(zapi, host_name): +    '''Get host id by name''' +    content = zapi.get_content('host', +                               'get', +                               {'filter': {'name': host_name}}) + +    return content['result'][0]['hostid'] + +def get_trigger_value(inc_trigger): +    '''determine the proper trigger value''' +    rval = 1 +    if inc_trigger == 'PROBLEM': +        rval = 1 +    else: +        rval = 0 + +    return rval + +def get_template_id_by_name(zapi, t_name): +    '''get the template id by name''' +    content = zapi.get_content('template', +                               'get', +                               {'filter': {'host': t_name}}) + +    return content['result'][0]['templateid'] + + +def get_host_group_id_by_name(zapi, hg_name): +    '''Get hostgroup id by name''' +    content = zapi.get_content('hostgroup', +                               'get', +                               {'filter': {'name': hg_name}}) + +    return content['result'][0]['groupid'] + +def get_condition_type(event_source, inc_condition): +    '''determine the condition type''' +    c_types = {} +    if event_source == 'trigger': +        c_types = {'host group': 0, +                   'host': 1, +                   'trigger': 2, +                   'trigger name': 3, +                   'trigger severity': 4, +                   'trigger value': 5, +                   'time period': 6, +                   'host template': 13, +                   'application': 15, +                   'maintenance status': 16, +                  } + +    elif event_source == 'discovery': +        c_types = {'host IP': 7, +                   'discovered service type': 8, +                   'discovered service port': 9, +                   'discovery status': 10, +                   'uptime or downtime duration': 11, +                   'received value': 12, +                   'discovery rule': 18, +                   'discovery check': 19, +                   'proxy': 20, +                   'discovery object': 21, +                  } + +    elif event_source == 'auto': +        c_types = {'proxy': 20, +                   'host name': 22, +                   'host metadata': 24, +                  } + +    elif event_source == 'internal': +        c_types = {'host group': 0, +                   'host': 1, +                   'host template': 13, +                   'application': 15, +                   'event type': 23, +                  } +    else: +        raise ZabbixAPIError('Unkown event source %s' % event_source) + +    return c_types[inc_condition] + +def get_operation_type(inc_operation): +    ''' determine the correct operation type''' +    o_types = {'send message': 0, +               'remote command': 1, +               'add host': 2, +               'remove host': 3, +               'add to host group': 4, +               'remove from host group': 5, +               'link to template': 6, +               'unlink from template': 7, +               'enable host': 8, +               'disable host': 9, +              } + +    return o_types[inc_operation] + +def get_action_operations(zapi, inc_operations): +    '''Convert the operations into syntax for api''' +    for operation in inc_operations: +        operation['operationtype'] = get_operation_type(operation['operationtype']) +        if operation['operationtype'] == 0: # send message.  Need to fix the +            operation['opmessage']['mediatypeid'] = \ +             get_mediatype_id_by_name(zapi, operation['opmessage']['mediatypeid']) +            operation['opmessage_grp'] = get_user_groups(zapi, operation.get('opmessage_grp', [])) +            operation['opmessage_usr'] = get_users(zapi, operation.get('opmessage_usr', [])) +            if operation['opmessage']['default_msg']: +                operation['opmessage']['default_msg'] = 1 +            else: +                operation['opmessage']['default_msg'] = 0 + +        # NOT supported for remote commands +        elif operation['operationtype'] == 1: +            continue + +        # Handle Operation conditions: +        # Currently there is only 1 available which +        # is 'event acknowledged'.  In the future +        # if there are any added we will need to pass this +        # option to a function and return the correct conditiontype +        if operation.has_key('opconditions'): +            for condition in operation['opconditions']: +                if condition['conditiontype'] == 'event acknowledged': +                    condition['conditiontype'] = 14 + +                if condition['operator'] == '=': +                    condition['operator'] = 0 + +                if condition['value'] == 'acknowledged': +                    condition['operator'] = 1 +                else: +                    condition['operator'] = 0 + + +    return inc_operations + +def get_operation_evaltype(inc_type): +    '''get the operation evaltype''' +    rval = 0 +    if inc_type == 'and/or': +        rval = 0 +    elif inc_type == 'and': +        rval = 1 +    elif inc_type == 'or': +        rval = 2 +    elif inc_type == 'custom': +        rval = 3 + +    return rval + +def get_action_conditions(zapi, event_source, inc_conditions): +    '''Convert the conditions into syntax for api''' + +    calc_type = inc_conditions.pop('calculation_type') +    inc_conditions['evaltype'] = get_operation_evaltype(calc_type) +    for cond in inc_conditions['conditions']: + +        cond['operator'] = get_condition_operator(cond['operator']) +        # Based on conditiontype we need to set the proper value +        # e.g. conditiontype = hostgroup then the value needs to be a hostgroup id +        # e.g. conditiontype = host the value needs to be a host id +        cond['conditiontype'] = get_condition_type(event_source, cond['conditiontype']) +        if cond['conditiontype'] == 0: +            cond['value'] = get_host_group_id_by_name(zapi, cond['value']) +        elif cond['conditiontype'] == 1: +            cond['value'] = get_host_id_by_name(zapi, cond['value']) +        elif cond['conditiontype'] == 4: +            cond['value'] = get_priority(cond['value']) + +        elif cond['conditiontype'] == 5: +            cond['value'] = get_trigger_value(cond['value']) +        elif cond['conditiontype'] == 13: +            cond['value'] = get_template_id_by_name(zapi, cond['value']) +        elif cond['conditiontype'] == 16: +            cond['value'] = '' + +    return inc_conditions + + +def get_send_recovery(send_recovery): +    '''Get the integer value''' +    rval = 0 +    if send_recovery: +        rval = 1 + +    return rval + +# The branches are needed for CRUD and error handling +# pylint: disable=too-many-branches +def main(): +    ''' +    ansible zabbix module for zbx_item +    ''' + + +    module = AnsibleModule( +        argument_spec=dict( +            zbx_server=dict(default='https://localhost/zabbix/api_jsonrpc.php', type='str'), +            zbx_user=dict(default=os.environ.get('ZABBIX_USER', None), type='str'), +            zbx_password=dict(default=os.environ.get('ZABBIX_PASSWORD', None), type='str'), +            zbx_debug=dict(default=False, type='bool'), + +            name=dict(default=None, type='str'), +            event_source=dict(default='trigger', choices=['trigger', 'discovery', 'auto', 'internal'], type='str'), +            action_subject=dict(default="{TRIGGER.NAME}: {TRIGGER.STATUS}", type='str'), +            action_message=dict(default="{TRIGGER.NAME}: {TRIGGER.STATUS}\r\n" + +                                "Last value: {ITEM.LASTVALUE}\r\n\r\n{TRIGGER.URL}", type='str'), +            reply_subject=dict(default="{TRIGGER.NAME}: {TRIGGER.STATUS}", type='str'), +            reply_message=dict(default="Trigger: {TRIGGER.NAME}\r\nTrigger status: {TRIGGER.STATUS}\r\n" + +                               "Trigger severity: {TRIGGER.SEVERITY}\r\nTrigger URL: {TRIGGER.URL}\r\n\r\n" + +                               "Item values:\r\n\r\n1. {ITEM.NAME1} ({HOST.NAME1}:{ITEM.KEY1}): " + +                               "{ITEM.VALUE1}\r\n2. {ITEM.NAME2} ({HOST.NAME2}:{ITEM.KEY2}): " + +                               "{ITEM.VALUE2}\r\n3. {ITEM.NAME3} ({HOST.NAME3}:{ITEM.KEY3}): " + +                               "{ITEM.VALUE3}", type='str'), +            send_recovery=dict(default=False, type='bool'), +            status=dict(default=None, type='str'), +            escalation_time=dict(default=60, type='int'), +            conditions_filter=dict(default=None, type='dict'), +            operations=dict(default=None, type='list'), +            state=dict(default='present', type='str'), +        ), +        #supports_check_mode=True +    ) + +    zapi = ZabbixAPI(ZabbixConnection(module.params['zbx_server'], +                                      module.params['zbx_user'], +                                      module.params['zbx_password'], +                                      module.params['zbx_debug'])) + +    #Set the instance and the template for the rest of the calls +    zbx_class_name = 'action' +    state = module.params['state'] + +    content = zapi.get_content(zbx_class_name, +                               'get', +                               {'search': {'name': module.params['name']}, +                                'selectFilter': 'extend', +                                'selectOperations': 'extend', +                               }) + +    #******# +    # GET +    #******# +    if state == 'list': +        module.exit_json(changed=False, results=content['result'], state="list") + +    #******# +    # DELETE +    #******# +    if state == 'absent': +        if not exists(content): +            module.exit_json(changed=False, state="absent") + +        content = zapi.get_content(zbx_class_name, 'delete', [content['result'][0]['itemid']]) +        module.exit_json(changed=True, results=content['result'], state="absent") + +    # Create and Update +    if state == 'present': + +        conditions = get_action_conditions(zapi, module.params['event_source'], module.params['conditions_filter']) +        operations = get_action_operations(zapi, module.params['operations']) +        params = {'name': module.params['name'], +                  'esc_period': module.params['escalation_time'], +                  'eventsource': get_event_source(module.params['event_source']), +                  'status': get_status(module.params['status']), +                  'def_shortdata': module.params['action_subject'], +                  'def_longdata': module.params['action_message'], +                  'r_shortdata': module.params['reply_subject'], +                  'r_longdata': module.params['reply_message'], +                  'recovery_msg': get_send_recovery(module.params['send_recovery']), +                  'filter': conditions, +                  'operations': operations, +                 } + +        # Remove any None valued params +        _ = [params.pop(key, None) for key in params.keys() if params[key] is None] + +        #******# +        # CREATE +        #******# +        if not exists(content): +            content = zapi.get_content(zbx_class_name, 'create', params) + +            if content.has_key('error'): +                module.exit_json(failed=True, changed=True, results=content['error'], state="present") + +            module.exit_json(changed=True, results=content['result'], state='present') + + +        ######## +        # UPDATE +        ######## +        _ = params.pop('hostid', None) +        differences = {} +        zab_results = content['result'][0] +        for key, value in params.items(): + +            if key == 'operations': +                ops = operation_differences(zab_results[key], value) +                if ops: +                    differences[key] = ops + +            elif key == 'filter': +                filters = filter_differences(zab_results[key], value) +                if filters: +                    differences[key] = filters + +            elif zab_results[key] != value and zab_results[key] != str(value): +                differences[key] = value + +        if not differences: +            module.exit_json(changed=False, results=zab_results, state="present") + +        # We have differences and need to update. +        # action update requires an id, filters, and operations +        differences['actionid'] = zab_results['actionid'] +        differences['operations'] = params['operations'] +        differences['filter'] = params['filter'] +        content = zapi.get_content(zbx_class_name, 'update', differences) + +        if content.has_key('error'): +            module.exit_json(failed=True, changed=False, results=content['error'], state="present") + +        module.exit_json(changed=True, results=content['result'], state="present") + +    module.exit_json(failed=True, +                     changed=False, +                     results='Unknown state passed. %s' % state, +                     state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets.  This are required +from ansible.module_utils.basic import * + +main() | 
