From 10c85e1e5f283f7e0fa788b43b022314284dc776 Mon Sep 17 00:00:00 2001
From: Kenny Woodson <kwoodson@redhat.com>
Date: Wed, 23 Sep 2015 17:31:05 -0400
Subject: Initial stab at zabbix actions

---
 roles/lib_zabbix/library/zbx_action.py | 538 +++++++++++++++++++++++++++++++++
 1 file changed, 538 insertions(+)
 create mode 100644 roles/lib_zabbix/library/zbx_action.py

(limited to 'roles/lib_zabbix/library')

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()
-- 
cgit v1.2.3