diff options
Diffstat (limited to 'roles')
| -rw-r--r-- | roles/lib_zabbix/library/zbx_action.py | 538 | ||||
| -rw-r--r-- | roles/lib_zabbix/library/zbx_user_media.py | 53 | ||||
| -rw-r--r-- | roles/lib_zabbix/tasks/create_template.yml | 28 | ||||
| -rw-r--r-- | roles/os_zabbix/vars/template_os_linux.yml | 18 | 
4 files changed, 628 insertions, 9 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() diff --git a/roles/lib_zabbix/library/zbx_user_media.py b/roles/lib_zabbix/library/zbx_user_media.py index 3f7760475..9ed838f81 100644 --- a/roles/lib_zabbix/library/zbx_user_media.py +++ b/roles/lib_zabbix/library/zbx_user_media.py @@ -54,8 +54,8 @@ def get_mtype(zapi, mtype):      except ValueError:          pass -    content = zapi.get_content('mediatype', 'get', {'search': {'description': mtype}}) -    if content.has_key['result'] and content['result']: +    content = zapi.get_content('mediatype', 'get', {'filter': {'description': mtype}}) +    if content.has_key('result') and content['result']:          return content['result'][0]['mediatypeid']      return None @@ -63,7 +63,7 @@ def get_mtype(zapi, mtype):  def get_user(zapi, user):      ''' Get userids from user aliases      ''' -    content = zapi.get_content('user', 'get', {'search': {'alias': user}}) +    content = zapi.get_content('user', 'get', {'filter': {'alias': user}})      if content['result']:          return content['result'][0] @@ -104,15 +104,17 @@ def find_media(medias, user_media):      ''' Find the user media in the list of medias      '''      for media in medias: -        if all([media[key] == user_media[key] for key in user_media.keys()]): +        if all([media[key] == str(user_media[key]) for key in user_media.keys()]):              return media      return None -def get_active(in_active): +def get_active(is_active):      '''Determine active value +       0 - enabled +       1 - disabled      '''      active = 1 -    if in_active: +    if is_active:          active = 0      return active @@ -128,6 +130,21 @@ def get_mediatype(zapi, mediatype, mediatype_desc):      return mtypeid +def preprocess_medias(zapi, medias): +    ''' Insert the correct information when processing medias ''' +    for media in medias: +        # Fetch the mediatypeid from the media desc (name) +        if media.has_key('mediatype'): +            media['mediatypeid'] = get_mediatype(zapi, mediatype=None, mediatype_desc=media.pop('mediatype')) + +        media['active'] = get_active(media.get('active')) +        media['severity'] = int(get_severity(media['severity'])) + +    return medias + +# Disabling branching as the logic requires branches. +# I've also added a few safeguards which required more branches. +# pylint: disable=too-many-branches  def main():      '''      Ansible zabbix module for mediatype @@ -166,11 +183,17 @@ def main():      # User media is fetched through the usermedia.get      zbx_user_query = get_zbx_user_query_data(zapi, module.params['login']) -    content = zapi.get_content('usermedia', 'get', zbx_user_query) - +    content = zapi.get_content('usermedia', 'get', +                               {'userids': [uid for user, uid in zbx_user_query.items()]}) +    ##### +    # Get +    #####      if state == 'list':          module.exit_json(changed=False, results=content['result'], state="list") +    ######## +    # Delete +    ########      if state == 'absent':          if not exists(content) or len(content['result']) == 0:              module.exit_json(changed=False, state="absent") @@ -178,13 +201,14 @@ def main():          if not module.params['login']:              module.exit_json(failed=True, changed=False, results='Must specifiy a user login.', state="absent") -        content = zapi.get_content(zbx_class_name, 'deletemedia', [content['result'][0][idname]]) +        content = zapi.get_content(zbx_class_name, 'deletemedia', [res[idname] for res in content['result']])          if content.has_key('error'):              module.exit_json(changed=False, results=content['error'], state="absent")          module.exit_json(changed=True, results=content['result'], state="absent") +    # Create and Update      if state == 'present':          active = get_active(module.params['active'])          mtypeid = get_mediatype(zapi, module.params['mediatype'], module.params['mediatype_desc']) @@ -197,13 +221,21 @@ def main():                         'severity': int(get_severity(module.params['severity'])),                         'period': module.params['period'],                        }] +        else: +            medias = preprocess_medias(zapi, medias)          params = {'users': [zbx_user_query],                    'medias': medias,                    'output': 'extend',                   } +        ######## +        # Create +        ########          if not exists(content): +            if not params['medias']: +                module.exit_json(changed=False, results=content['result'], state='present') +              # if we didn't find it, create it              content = zapi.get_content(zbx_class_name, 'addmedia', params) @@ -216,6 +248,9 @@ def main():          # If user params exists, check to see if they already exist in zabbix          # if they exist, then return as no update          # elif they do not exist, then take user params only +        ######## +        # Update +        ########          diff = {'medias': [], 'users': {}}          _ = [diff['medias'].append(media) for media in params['medias'] if not find_media(content['result'], media)] diff --git a/roles/lib_zabbix/tasks/create_template.yml b/roles/lib_zabbix/tasks/create_template.yml index fd0cdd46f..b4821bdc7 100644 --- a/roles/lib_zabbix/tasks/create_template.yml +++ b/roles/lib_zabbix/tasks/create_template.yml @@ -52,3 +52,31 @@      url: "{{ item.url | default(None, True) }}"    with_items: template.ztriggers    when: template.ztriggers is defined + +- name: Create Discoveryrules +  zbx_discoveryrule: +    zbx_server: "{{ server }}" +    zbx_user: "{{ user }}" +    zbx_password: "{{ password }}" +    name: "{{ item.name }}" +    key: "{{ item.key }}" +    lifetime: "{{ item.lifetime }}" +    template_name: "{{ template.name }}" +    description: "{{ item.description | default('', True) }}" +  with_items: template.zdiscoveryrules +  when: template.zdiscoveryrules is defined + +- name: Create Item Prototype +  zbx_itemprototype: +    zbx_server: "{{ server }}" +    zbx_user: "{{ user }}" +    zbx_password: "{{ password }}" +    name: "{{ item.name }}" +    key: "{{ item.key }}" +    discoveryrule_key: "{{ item.discoveryrule_key }}" +    value_type: "{{ item.value_type }}" +    template_name: "{{ template.name }}" +    applications: "{{ item.applications }}" +    description: "{{ item.description | default('', True) }}" +  with_items: template.zitemprototypes +  when: template.zitemprototypes is defined diff --git a/roles/os_zabbix/vars/template_os_linux.yml b/roles/os_zabbix/vars/template_os_linux.yml index 3173c79b2..84a7740b0 100644 --- a/roles/os_zabbix/vars/template_os_linux.yml +++ b/roles/os_zabbix/vars/template_os_linux.yml @@ -191,6 +191,24 @@ g_template_os_linux:      - Disk      value_type: float + +  zdiscoveryrules: +  - name: disc.filesys +    key: disc.filesys +    lifetime: 1 +    template_name: Template OS Linux +    description: "Dynamically register the filesystems" + +  zitemprototypes: +  - discoveryrule_key: disc.filesys +    template_name: Template OS Linux +    name: "disc.filesys.full.{#OSO_FILESYS}" +    key: "disc.filesys.full[{#OSO_FILESYS}]" +    value_type: float +    description: "PCP filesys.full option.  This is the percent full returned from pcp filesys.full" +    applications: +    - Disk +    ztriggers:    - name: 'Filesystem: / has less than 10% free on {HOST.NAME}'      expression: '{Template OS Linux:filesys.full.xvda2.last()}>90'  | 
