Add state modules for Zabbix templates, actions and valuemaps

This commit is contained in:
slivik 2018-04-30 14:44:05 +02:00
parent 8f3f3146ba
commit e5cf77d3d1
No known key found for this signature in database
GPG Key ID: C3EA0559C87216D6
11 changed files with 2077 additions and 33 deletions

View File

@ -0,0 +1,5 @@
salt.states.zabbix_action module
==============================
.. automodule:: salt.states.zabbix_action
:members:

View File

@ -0,0 +1,5 @@
salt.states.zabbix_template module
==============================
.. automodule:: salt.states.zabbix_template
:members:

View File

@ -0,0 +1,5 @@
salt.states.zabbix_valuemap module
==============================
.. automodule:: salt.states.zabbix_valuemap
:members:

View File

@ -23,34 +23,91 @@ Support for Zabbix
:codeauthor: Jiri Kotlin <jiri.kotlin@ultimum.io>
'''
from __future__ import absolute_import, print_function, unicode_literals
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
# Import python libs
import logging
import socket
import json
import os
# Import salt libs
from salt.ext import six
import salt.utils.http
import salt.utils.json
import salt.utils.path
from salt.utils.versions import LooseVersion as _LooseVersion
from salt.ext.six.moves.urllib.error import HTTPError, URLError # pylint: disable=import-error,no-name-in-module
try:
import salt.utils
from salt.ext import six
from salt.utils.versions import LooseVersion as _LooseVersion
# pylint: disable=import-error,no-name-in-module,unused-import
from salt.ext.six.moves.urllib.error import HTTPError, URLError
from salt.exceptions import SaltException
IMPORTS_OK = True
except ImportError:
IMPORTS_OK = False
log = logging.getLogger(__name__)
INTERFACE_DEFAULT_PORTS = [10050, 161, 623, 12345]
ZABBIX_TOP_LEVEL_OBJECTS = ('hostgroup', 'template', 'host', 'maintenance', 'action', 'drule', 'service', 'proxy',
'screen', 'usergroup', 'mediatype', 'script', 'valuemap')
# Zabbix object and its ID name mapping
ZABBIX_ID_MAPPER = {
'action': 'actionid',
'alert': 'alertid',
'application': 'applicationid',
'dhost': 'dhostid',
'dservice': 'dserviceid',
'dcheck': 'dcheckid',
'drule': 'druleid',
'event': 'eventid',
'graph': 'graphid',
'graphitem': 'gitemid',
'graphprototype': 'graphid',
'history': 'itemid',
'host': 'hostid',
'hostgroup': 'groupid',
'hostinterface': 'interfaceid',
'hostprototype': 'hostid',
'iconmap': 'iconmapid',
'image': 'imageid',
'item': 'itemid',
'itemprototype': 'itemid',
'service': 'serviceid',
'discoveryrule': 'itemid',
'maintenance': 'maintenanceid',
'map': 'sysmapid',
'usermedia': 'mediaid',
'mediatype': 'mediatypeid',
'proxy': 'proxyid',
'screen': 'screenid',
'screenitem': 'screenitemid',
'script': 'scriptid',
'template': 'templateid',
'templatescreen': 'screenid',
'templatescreenitem': 'screenitemid',
'trend': 'itemid',
'trigger': 'triggerid',
'triggerprototype': 'triggerid',
'user': 'userid',
'usergroup': 'usrgrpid',
'usermacro': 'globalmacroid',
'valuemap': 'valuemapid',
'httptest': 'httptestid'
}
# Define the module's virtual name
__virtualname__ = 'zabbix'
def __virtual__():
'''
Only load the module if Zabbix server is installed
Only load the module if all modules are imported correctly.
'''
if salt.utils.path.which('zabbix_server'):
if IMPORTS_OK:
return __virtualname__
return (False, 'The zabbix execution module cannot be loaded: zabbix not installed.')
return False, 'Importing modules failed.'
def _frontend_url():
@ -86,7 +143,9 @@ def _query(method, params, url, auth=None):
:param url: url of zabbix api
:param auth: auth token for zabbix api (only for methods with required authentication)
:return: Response from API with desired data in JSON format.
:return: Response from API with desired data in JSON format. In case of error returns more specific description.
.. versionchanged:: 2017.7
'''
unauthenticated_methods = ['user.login', 'apiinfo.version', ]
@ -99,17 +158,28 @@ def _query(method, params, url, auth=None):
data = salt.utils.json.dumps(data)
log.info('_QUERY input:\nurl: %s\ndata: %s', six.text_type(url), six.text_type(data))
try:
result = salt.utils.http.query(url,
method='POST',
data=data,
header_dict=header_dict,
decode_type='json',
decode=True,)
decode=True,
status=True,
headers=True)
log.info('_QUERY result: %s', six.text_type(result))
if 'error' in result:
raise SaltException('Zabbix API: Status: {0} ({1})'.format(result['status'], result['error']))
ret = result.get('dict', {})
if 'error' in ret:
raise SaltException('Zabbix API: {} ({})'.format(ret['error']['message'], ret['error']['data']))
return ret
except (URLError, socket.gaierror):
return {}
except ValueError as err:
raise SaltException('URL or HTTP headers are probably not correct! ({})'.format(err))
except socket.error as err:
raise SaltException('Check hostname in URL! ({})'.format(err))
def _login(**kwargs):
@ -171,8 +241,8 @@ def _login(**kwargs):
return connargs
else:
raise KeyError
except KeyError:
return False
except KeyError as err:
raise SaltException('URL is probably not correct! ({})'.format(err))
def _params_extend(params, _ignore_name=False, **kwargs):
@ -208,6 +278,160 @@ def _params_extend(params, _ignore_name=False, **kwargs):
return params
def get_zabbix_id_mapper():
'''
.. versionadded:: 2017.7
Make ZABBIX_ID_MAPPER constant available to state modules.
:return: ZABBIX_ID_MAPPER
'''
return ZABBIX_ID_MAPPER
def substitute_params(input_object, extend_params=None, filter_key='name', **kwargs):
'''
.. versionadded:: 2017.7
Go through Zabbix object params specification and if needed get given object ID from Zabbix API and put it back
as a value. Definition of the object is done via dict with keys "query_object" and "query_name".
:param input_object: Zabbix object type specified in state file
:param extend_params: Specify query with params
:param filter_key: Custom filtering key (default: name)
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
:return: Params structure with values converted to string for further comparison purposes
'''
if extend_params is None:
extend_params = {}
if isinstance(input_object, list):
return [substitute_params(oitem, extend_params, filter_key, **kwargs) for oitem in input_object]
elif isinstance(input_object, dict):
if 'query_object' in input_object:
query_params = {}
if input_object['query_object'] not in ZABBIX_TOP_LEVEL_OBJECTS:
query_params.update(extend_params)
try:
query_params.update({'filter': {filter_key: input_object['query_name']}})
return get_object_id_by_params(input_object['query_object'], query_params, **kwargs)
except KeyError:
raise SaltException('Qyerying object ID requested '
'but object name not provided: {0}'.format(input_object))
else:
return {key: substitute_params(val, extend_params, filter_key, **kwargs)
for key, val in input_object.items()}
else:
# Zabbix response is always str, return everything in str as well
return six.text_type(input_object)
# pylint: disable=too-many-return-statements
def compare_params(defined, existing, return_old_value=False):
'''
.. versionadded:: 2017.7
Compares Zabbix object definition against existing Zabbix object.
:param defined: Zabbix object definition taken from sls file.
:param existing: Existing Zabbix object taken from result of an API call.
:param return_old_value: Default False. If True, returns dict("old"=old_val, "new"=new_val) for rollback purpose.
:return: Params that are different from existing object. Result extended by object ID can be passed directly to
Zabbix API update method.
'''
# Comparison of data types
if not isinstance(defined, type(existing)):
raise SaltException('Zabbix object comparison failed (data type mismatch). Expecting {0}, got {1}. '
'Existing value: "{2}", defined value: "{3}").'.format(type(existing),
type(defined),
existing,
defined))
# Comparison of values
if not salt.utils.is_iter(defined):
if six.text_type(defined) != six.text_type(existing) and return_old_value:
return {'new': six.text_type(defined), 'old': six.text_type(existing)}
elif six.text_type(defined) != six.text_type(existing) and not return_old_value:
return six.text_type(defined)
# Comparison of lists of values or lists of dicts
if isinstance(defined, list):
if len(defined) != len(existing):
log.info('Different list length!')
return {'new': defined, 'old': existing} if return_old_value else defined
else:
difflist = []
for ditem in defined:
d_in_e = []
for eitem in existing:
comp = compare_params(ditem, eitem, return_old_value)
if return_old_value:
d_in_e.append(comp['new'])
else:
d_in_e.append(comp)
if all(d_in_e):
difflist.append(ditem)
# If there is any difference in a list then whole defined list must be returned and provided for update
if any(difflist) and return_old_value:
return {'new': defined, 'old': existing}
elif any(difflist) and not return_old_value:
return defined
# Comparison of dicts
if isinstance(defined, dict):
try:
# defined must be a subset of existing to be compared
if set(defined) <= set(existing):
intersection = set(defined) & set(existing)
diffdict = {'new': {}, 'old': {}} if return_old_value else {}
for i in intersection:
comp = compare_params(defined[i], existing[i], return_old_value)
if return_old_value:
if comp or (not comp and isinstance(comp, list)):
diffdict['new'].update({i: defined[i]})
diffdict['old'].update({i: existing[i]})
else:
if comp or (not comp and isinstance(comp, list)):
diffdict.update({i: defined[i]})
return diffdict
return {'new': defined, 'old': existing} if return_old_value else defined
except TypeError:
raise SaltException('Zabbix object comparison failed (data type mismatch). Expecting {0}, got {1}. '
'Existing value: "{2}", defined value: "{3}").'.format(type(existing),
type(defined),
existing,
defined))
def get_object_id_by_params(obj, params=None, **connection_args):
'''
.. versionadded:: 2017.7
Get ID of single Zabbix object specified by its name.
:param obj: Zabbix object type
:param params: Parameters by which object is uniquely identified
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
:return: object ID
'''
if params is None:
params = {}
res = run_query(obj + '.get', params, **connection_args)
if res and len(res) == 1:
return six.text_type(res[0][ZABBIX_ID_MAPPER[obj]])
else:
raise SaltException('Zabbix API: Object does not exist or bad Zabbix user permissions or other unexpected '
'result. Called method {0} with params {1}. '
'Result: {2}'.format(obj + '.get', params, res))
def apiinfo_version(**connection_args):
'''
Retrieve the version of the Zabbix API.
@ -846,8 +1070,8 @@ def host_create(host, groups, interfaces, **connection_args):
.. code-block:: bash
salt '*' zabbix.host_create technicalname 4
interfaces='{type: 1, main: 1, useip: 1, ip: "192.168.3.1", dns: "", port: 10050}'
visible_name='Host Visible Name' inventory_mode=0 inventory='{"alias": "something"}'
interfaces='{if_type: 1, main: 1, useip: 1, ip_: "192.168.3.1", dns: "", port: 10050}'
visible_name='Host Visible Name'
'''
conn_args = _login(**connection_args)
ret = False
@ -1466,7 +1690,7 @@ def hostinterface_get(hostids, **connection_args):
return ret
def hostinterface_create(hostid, ip, dns='', main=1, type=1, useip=1, port=None, **connection_args):
def hostinterface_create(hostid, ip_, dns='', main=1, if_type=1, useip=1, port=None, **connection_args):
'''
Create new host interface
NOTE: This function accepts all standard host group interface: keyword argument names differ depending
@ -1475,11 +1699,11 @@ def hostinterface_create(hostid, ip, dns='', main=1, type=1, useip=1, port=None,
.. versionadded:: 2016.3.0
:param hostid: ID of the host the interface belongs to
:param ip: IP address used by the interface
:param ip_: IP address used by the interface
:param dns: DNS name used by the interface
:param main: whether the interface is used as default on the host (0 - not default, 1 - default)
:param port: port number used by the interface
:param type: Interface type (1 - agent; 2 - SNMP; 3 - IPMI; 4 - JMX)
:param if_type: Interface type (1 - agent; 2 - SNMP; 3 - IPMI; 4 - JMX)
:param useip: Whether the connection should be made via IP (0 - connect using host DNS name; 1 - connect using
host IP address for this host interface)
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
@ -1497,12 +1721,18 @@ def hostinterface_create(hostid, ip, dns='', main=1, type=1, useip=1, port=None,
ret = False
if not port:
port = INTERFACE_DEFAULT_PORTS[type]
port = INTERFACE_DEFAULT_PORTS[if_type]
try:
if conn_args:
method = 'hostinterface.create'
params = {"hostid": hostid, "ip": ip, "dns": dns, "main": main, "port": port, "type": type, "useip": useip}
params = {"hostid": hostid,
"ip": ip_,
"dns": dns,
"main": main,
"port": port,
"type": if_type,
"useip": useip}
params = _params_extend(params, **connection_args)
ret = _query(method, params, conn_args['url'], conn_args['auth'])
return ret['result']['interfaceids']
@ -1565,7 +1795,7 @@ def hostinterface_update(interfaceid, **connection_args):
CLI Example:
.. code-block:: bash
salt '*' zabbix.hostinterface_update 6 ip=0.0.0.2
salt '*' zabbix.hostinterface_update 6 ip_=0.0.0.2
'''
conn_args = _login(**connection_args)
ret = False
@ -1879,8 +2109,9 @@ def mediatype_get(name=None, mediatypeids=None, **connection_args):
_connection_password: zabbix password (can also be set in opts or pillar, see module's docstring)
_connection_url: url of zabbix frontend (can also be set in opts or pillar, see module's docstring)
all optional mediatype.get parameters: keyword argument names differ depending on your zabbix
version,nsee: https://www.zabbix.com/documentation/2.2/manual/api/reference/mediatype/get
all optional mediatype.get parameters: keyword argument names depends on your zabbix version, see:
https://www.zabbix.com/documentation/2.2/manual/api/reference/mediatype/get
Returns:
Array with mediatype details, False if no mediatype found or on failure.
@ -2039,8 +2270,9 @@ def template_get(name=None, host=None, templateids=None, **connection_args):
_connection_password: zabbix password (can also be set in opts or pillar, see module's docstring)
_connection_url: url of zabbix frontend (can also be set in opts or pillar, see module's docstring)
all optional template.get parameters: keyword argument names differ depending on your zabbix
version, see: https://www.zabbix.com/documentation/2.4/manual/api/reference/template/get
all optional template.get parameters: keyword argument names depends on your zabbix version, see:
https://www.zabbix.com/documentation/2.4/manual/api/reference/template/get
Returns:
Array with convenient template details, False if no template found or on failure.
@ -2085,8 +2317,9 @@ def run_query(method, params, **connection_args):
_connection_password: zabbix password (can also be set in opts or pillar, see module's docstring)
_connection_url: url of zabbix frontend (can also be set in opts or pillar, see module's docstring)
all optional template.get parameters: keyword argument names differ depending on your zabbix
version, see: https://www.zabbix.com/documentation/2.4/manual/api/reference/
all optional template.get parameters: keyword argument names depends on your zabbix version, see:
https://www.zabbix.com/documentation/2.4/manual/api/reference/
Returns:
Response from Zabbix API
@ -2100,10 +2333,86 @@ def run_query(method, params, **connection_args):
ret = False
try:
if conn_args:
method = method
params = params
params = _params_extend(params, **connection_args)
ret = _query(method, params, conn_args['url'], conn_args['auth'])
if isinstance(ret['result'], bool):
return ret['result']
return ret['result'] if len(ret['result']) > 0 else False
else:
raise KeyError
except KeyError:
return ret
def configuration_import(config_file, rules=None, file_format='xml', **connection_args):
'''
.. versionadded:: 2017.7
Imports Zabbix configuration sepcified in file to Zabbix server.
:param config_file: File with Zabbix config (local or remote)
:param rules: Optional - Rules that have to be different from default (defaults are the same as in Zabbix web UI.)
:param file_format: Config file format (default: xml)
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
CLI Example:
.. code-block:: bash
salt '*' zabbix.configuration_import salt://zabbix/config/zabbix_templates.xml \
"{'screens': {'createMissing': True, 'updateExisting': True}}"
'''
if rules is None:
rules = {}
default_rules = {'applications': {'createMissing': True, 'updateExisting': False, 'deleteMissing': False},
'discoveryRules': {'createMissing': True, 'updateExisting': True, 'deleteMissing': False},
'graphs': {'createMissing': True, 'updateExisting': True, 'deleteMissing': False},
'groups': {'createMissing': True},
'hosts': {'createMissing': False, 'updateExisting': False},
'images': {'createMissing': False, 'updateExisting': False},
'items': {'createMissing': True, 'updateExisting': True, 'deleteMissing': False},
'maps': {'createMissing': False, 'updateExisting': False},
'screens': {'createMissing': False, 'updateExisting': False},
'templateLinkage': {'createMissing': True},
'templates': {'createMissing': True, 'updateExisting': True},
'templateScreens': {'createMissing': True, 'updateExisting': True, 'deleteMissing': False},
'triggers': {'createMissing': True, 'updateExisting': True, 'deleteMissing': False},
'valueMaps': {'createMissing': True, 'updateExisting': False}}
new_rules = dict(default_rules)
if rules:
for rule in rules:
if rule in new_rules:
new_rules[rule].update(rules[rule])
else:
new_rules[rule] = rules[rule]
if 'salt://' in config_file:
tmpfile = salt.utils.mkstemp()
cfile = __salt__['cp.get_file'](config_file, tmpfile)
if not cfile or os.path.getsize(cfile) == 0:
return {'name': config_file, 'result': False, 'message': 'Failed to fetch config file.'}
else:
cfile = config_file
if not os.path.isfile(cfile):
return {'name': config_file, 'result': False, 'message': 'Invalid file path.'}
with salt.utils.fopen(cfile, mode='r') as fp_:
xml = fp_.read()
if 'salt://' in config_file:
salt.utils.safe_rm(cfile)
params = {'format': file_format,
'rules': new_rules,
'source': xml}
log.info('CONFIGURATION IMPORT: rules: %s', six.text_type(params['rules']))
try:
run_query('configuration.import', params, **connection_args)
return {'name': config_file, 'result': True, 'message': 'Zabbix API "configuration.import" method '
'called successfully.'}
except SaltException as exc:
return {'name': config_file, 'result': False, 'message': six.text_type(exc)}

View File

@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
'''
.. versionadded:: 2017.7
Management of Zabbix Action object over Zabbix API.
:codeauthor: Jakub Sliva <jakub.sliva@ultimum.io>
'''
from __future__ import absolute_import
from __future__ import unicode_literals
import logging
import json
try:
from salt.ext import six
from salt.exceptions import SaltException
IMPORTS_OK = True
except ImportError:
IMPORTS_OK = False
log = logging.getLogger(__name__)
def __virtual__():
'''
Only make these states available if Zabbix module and run_query function is available
and all 3rd party modules imported.
'''
if 'zabbix.run_query' in __salt__ and IMPORTS_OK:
return True
return False, 'Import zabbix or other needed modules failed.'
def present(name, params, **kwargs):
'''
Creates Zabbix Action object or if differs update it according defined parameters
:param name: Zabbix Action name
:param params: Definition of the Zabbix Action
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
If there is a need to get a value from current zabbix online (e.g. id of a hostgroup you want to put a discovered
system into), put a dictionary with two keys "query_object" and "query_name" instead of the value.
In this example we want to get object id of hostgroup named "Virtual machines" and "Databases".
.. code-block:: yaml
zabbix-action-present:
zabbix_action.present:
- name: VMs
- params:
eventsource: 2
status: 0
filter:
evaltype: 2
conditions:
- conditiontype: 24
operator: 2
value: 'virtual'
- conditiontype: 24
operator: 2
value: 'kvm'
operations:
- operationtype: 2
- operationtype: 4
opgroup:
- groupid:
query_object: hostgroup
query_name: Virtual machines
- groupid:
query_object: hostgroup
query_name: Databases
'''
zabbix_id_mapper = __salt__['zabbix.get_zabbix_id_mapper']()
dry_run = __opts__['test']
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
# Create input params substituting functions with their results
params['name'] = name
params['operations'] = params['operations'] if 'operations' in params else []
if 'filter' in params:
params['filter']['conditions'] = params['filter']['conditions'] if 'conditions' in params['filter'] else []
input_params = __salt__['zabbix.substitute_params'](params, **kwargs)
log.info('Zabbix Action: input params: %s', six.text_type(json.dumps(input_params, indent=4)))
search = {'output': 'extend',
'selectOperations': 'extend',
'selectFilter': 'extend',
'filter': {
'name': name
}}
# GET Action object if exists
action_get = __salt__['zabbix.run_query']('action.get', search, **kwargs)
log.info('Zabbix Action: action.get result: %s', six.text_type(json.dumps(action_get, indent=4)))
existing_obj = __salt__['zabbix.substitute_params'](action_get[0], **kwargs) \
if action_get and len(action_get) == 1 else False
if existing_obj:
diff_params = __salt__['zabbix.compare_params'](input_params, existing_obj)
log.info('Zabbix Action: input params: {%s', six.text_type(json.dumps(input_params, indent=4)))
log.info('Zabbix Action: Object comparison result. Differences: %s', six.text_type(diff_params))
if diff_params:
diff_params[zabbix_id_mapper['action']] = existing_obj[zabbix_id_mapper['action']]
# diff_params['name'] = 'VMs' - BUG - https://support.zabbix.com/browse/ZBX-12078
log.info('Zabbix Action: update params: %s', six.text_type(json.dumps(diff_params, indent=4)))
if dry_run:
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" would be fixed.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" differs '
'in following parameters: {1}'.format(name, diff_params),
'new': 'Zabbix Action "{0}" would correspond to definition.'.format(name)}}
else:
action_update = __salt__['zabbix.run_query']('action.update', diff_params, **kwargs)
log.info('Zabbix Action: action.update result: %s', six.text_type(action_update))
if action_update:
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" updated.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" differed '
'in following parameters: {1}'.format(name, diff_params),
'new': 'Zabbix Action "{0}" fixed.'.format(name)}}
else:
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" already exists and corresponds to a definition.'.format(name)
else:
if dry_run:
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" would be created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" does not exist.'.format(name),
'new': 'Zabbix Action "{0}" would be created according definition.'.format(name)}}
else:
# ACTION.CREATE
action_create = __salt__['zabbix.run_query']('action.create', input_params, **kwargs)
log.info('Zabbix Action: action.create result: ' + six.text_type(action_create))
if action_create:
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" did not exist.'.format(name),
'new': 'Zabbix Action "{0}" created according definition.'.format(name)}}
return ret
def absent(name, **kwargs):
'''
Makes the Zabbix Action to be absent (either does not exist or delete it).
:param name: Zabbix Action name
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
.. code-block:: yaml
zabbix-action-absent:
zabbix_action.absent:
- name: Action name
'''
dry_run = __opts__['test']
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
try:
object_id = __salt__['zabbix.get_object_id_by_params']('action', {'filter': {'name': name}}, **kwargs)
except SaltException:
object_id = False
if not object_id:
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" does not exist.'.format(name)
else:
if dry_run:
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" would be deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" exists.'.format(name),
'new': 'Zabbix Action "{0}" would be deleted.'.format(name)}}
else:
action_delete = __salt__['zabbix.run_query']('action.delete', [object_id], **kwargs)
if action_delete:
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" existed.'.format(name),
'new': 'Zabbix Action "{0}" deleted.'.format(name)}}
return ret

View File

@ -0,0 +1,708 @@
# -*- coding: utf-8 -*-
'''
.. versionadded:: 2017.7
Management of Zabbix Template object over Zabbix API.
:codeauthor: Jakub Sliva <jakub.sliva@ultimum.io>
'''
from __future__ import absolute_import
from __future__ import unicode_literals
import logging
import json
try:
from salt.ext import six
from salt.exceptions import SaltException
IMPORTS_OK = True
except ImportError:
IMPORTS_OK = False
log = logging.getLogger(__name__)
TEMPLATE_RELATIONS = ['groups', 'hosts', 'macros']
TEMPLATE_COMPONENT_ORDER = ('applications',
'items',
'triggers',
'gitems',
'graphs',
'screens',
'httpTests',
'discoveries')
DISCOVERYRULE_COMPONENT_ORDER = ('itemprototypes', 'triggerprototypes', 'graphprototypes', 'hostprototypes')
TEMPLATE_COMPONENT_DEF = {
# 'component': {'qtype': 'component type to query',
# 'qidname': 'component id name',
# 'qselectpid': 'particular component selection attribute name (parent id name)',
# 'ptype': 'parent component type',
# 'pid': 'parent component id',
# 'pid_ref_name': 'component's creation reference name for parent id',
# 'res_id_name': 'jsonrpc modification call result key name of list of affected IDs'},
# 'output': {'output': 'extend', 'selectApplications': 'extend', 'templated': 'true'},
# 'inherited': 'attribute name for inheritance toggling',
# 'filter': 'child component unique identification attribute name',
'applications': {'qtype': 'application',
'qidname': 'applicationid',
'qselectpid': 'templateids',
'ptype': 'template',
'pid': 'templateid',
'pid_ref_name': 'hostid',
'res_id_name': 'applicationids',
'output': {'output': 'extend', 'templated': 'true'},
'inherited': 'inherited',
'adjust': True,
'filter': 'name',
'ro_attrs': ['applicationid', 'flags', 'templateids']},
'items': {'qtype': 'item',
'qidname': 'itemid',
'qselectpid': 'templateids',
'ptype': 'template',
'pid': 'templateid',
'pid_ref_name': 'hostid',
'res_id_name': 'itemids',
'output': {'output': 'extend', 'selectApplications': 'extend', 'templated': 'true'},
'inherited': 'inherited',
'adjust': False,
'filter': 'name',
'ro_attrs': ['itemid', 'error', 'flags', 'lastclock', 'lastns',
'lastvalue', 'prevvalue', 'state', 'templateid']},
'triggers': {'qtype': 'trigger',
'qidname': 'triggerid',
'qselectpid': 'templateids',
'ptype': 'template',
'pid': 'templateid',
'pid_ref_name': None,
'res_id_name': 'triggerids',
'output': {'output': 'extend', 'selectDependencies': 'expand',
'templated': 'true', 'expandExpression': 'true'},
'inherited': 'inherited',
'adjust': False,
'filter': 'description',
'ro_attrs': ['error', 'flags', 'lastchange', 'state', 'templateid', 'value']},
'graphs': {'qtype': 'graph',
'qidname': 'graphid',
'qselectpid': 'templateids',
'ptype': 'template',
'pid': 'templateid',
'pid_ref_name': None,
'res_id_name': 'graphids',
'output': {'output': 'extend', 'selectGraphItems': 'extend', 'templated': 'true'},
'inherited': 'inherited',
'adjust': False,
'filter': 'name',
'ro_attrs': ['graphid', 'flags', 'templateid']},
'gitems': {'qtype': 'graphitem',
'qidname': 'itemid',
'qselectpid': 'graphids',
'ptype': 'graph',
'pid': 'graphid',
'pid_ref_name': None,
'res_id_name': None,
'output': {'output': 'extend'},
'inherited': 'inherited',
'adjust': False,
'filter': 'name',
'ro_attrs': ['gitemid']},
# "Template screen"
'screens': {'qtype': 'templatescreen',
'qidname': 'screenid',
'qselectpid': 'templateids',
'ptype': 'template',
'pid': 'templateid',
'pid_ref_name': 'templateid',
'res_id_name': 'screenids',
'output': {'output': 'extend', 'selectUsers': 'extend', 'selectUserGroups': 'extend',
'selectScreenItems': 'extend', 'noInheritance': 'true'},
'inherited': 'noInheritance',
'adjust': False,
'filter': 'name',
'ro_attrs': ['screenid']},
# "LLD rule"
'discoveries': {'qtype': 'discoveryrule',
'qidname': 'itemid',
'qselectpid': 'templateids',
'ptype': 'template',
'pid': 'templateid',
'pid_ref_name': 'hostid',
'res_id_name': 'itemids',
'output': {'output': 'extend', 'selectFilter': 'extend', 'templated': 'true'},
'inherited': 'inherited',
'adjust': False,
'filter': 'key_',
'ro_attrs': ['itemid', 'error', 'state', 'templateid']},
# "Web scenario"
'httpTests': {'qtype': 'httptest',
'qidname': 'httptestid',
'qselectpid': 'templateids',
'ptype': 'template',
'pid': 'templateid',
'pid_ref_name': 'hostid',
'res_id_name': 'httptestids',
'output': {'output': 'extend', 'selectSteps': 'extend', 'templated': 'true'},
'inherited': 'inherited',
'adjust': False,
'filter': 'name',
'ro_attrs': ['httptestid', 'nextcheck', 'templateid']},
# discoveries => discoveryrule
'itemprototypes': {'qtype': 'itemprototype',
'qidname': 'itemid',
'qselectpid': 'discoveryids',
'ptype': 'discoveryrule',
'pid': 'itemid',
'pid_ref_name': 'ruleid',
# exception only in case of itemprototype - needs both parent ruleid and hostid
'pid_ref_name2': 'hostid',
'res_id_name': 'itemids',
'output': {'output': 'extend', 'selectSteps': 'extend', 'selectApplications': 'extend',
'templated': 'true'},
'adjust': False,
'inherited': 'inherited',
'filter': 'name',
'ro_attrs': ['itemid', 'templateid']},
'triggerprototypes': {'qtype': 'triggerprototype',
'qidname': 'triggerid',
'qselectpid': 'discoveryids',
'ptype': 'discoveryrule',
'pid': 'itemid',
'pid_ref_name': None,
'res_id_name': 'triggerids',
'output': {'output': 'extend', 'selectTags': 'extend', 'selectDependencies': 'extend',
'templated': 'true', 'expandExpression': 'true'},
'inherited': 'inherited',
'adjust': False,
'filter': 'description',
'ro_attrs': ['triggerid', 'templateid']},
'graphprototypes': {'qtype': 'graphprototype',
'qidname': 'graphid',
'qselectpid': 'discoveryids',
'ptype': 'discoveryrule',
'pid': 'itemid',
'pid_ref_name': None,
'res_id_name': 'graphids',
'output': {'output': 'extend', 'selectGraphItems': 'extend', 'templated': 'true'},
'inherited': 'inherited',
'adjust': False,
'filter': 'name',
'ro_attrs': ['graphid', 'templateid']},
'hostprototypes': {'qtype': 'hostprototype',
'qidname': 'hostid',
'qselectpid': 'discoveryids',
'ptype': 'discoveryrule',
'pid': 'itemid',
'pid_ref_name': 'ruleid',
'res_id_name': 'hostids',
'output': {'output': 'extend', 'selectGroupLinks': 'expand', 'selectGroupPrototypes': 'expand',
'selectTemplates': 'expand'},
'inherited': 'inherited',
'adjust': False,
'filter': 'host',
'ro_attrs': ['hostid', 'templateid']}
}
# CHANGE_STACK = [{'component': 'items', 'action': 'create', 'params': dict|list}]
CHANGE_STACK = []
def __virtual__():
'''
Only make these states available if Zabbix module and run_query function is available
and all 3rd party modules imported.
'''
if 'zabbix.run_query' in __salt__ and IMPORTS_OK:
return True
return False, 'Import zabbix or other needed modules failed.'
def _diff_and_merge_host_list(defined, existing):
'''
If Zabbix template is to be updated then list of assigned hosts must be provided in all or nothing manner to prevent
some externally assigned hosts to be detached.
:param defined: list of hosts defined in sls
:param existing: list of hosts taken from live Zabbix
:return: list to be updated (combinated or empty list)
'''
try:
defined_host_ids = set([host['hostid'] for host in defined])
existing_host_ids = set([host['hostid'] for host in existing])
except KeyError:
raise SaltException('List of hosts in template not defined correctly.')
diff = defined_host_ids - existing_host_ids
return [{'hostid': six.text_type(hostid)} for hostid in diff | existing_host_ids] if diff else []
def _get_existing_template_c_list(component, parent_id, **kwargs):
'''
Make a list of given component type not inherited from other templates because Zabbix API returns only list of all
and list of inherited component items so we have to do a difference list.
:param component: Template component (application, item, etc...)
:param parent_id: ID of existing template the component is assigned to
:return List of non-inherited (own) components
'''
c_def = TEMPLATE_COMPONENT_DEF[component]
q_params = dict(c_def['output'])
q_params.update({c_def['qselectpid']: parent_id})
existing_clist_all = __salt__['zabbix.run_query'](c_def['qtype'] + '.get', q_params, **kwargs)
# in some cases (e.g. templatescreens) the logic is reversed (even name of the flag is different!)
if c_def['inherited'] == 'inherited':
q_params.update({c_def['inherited']: 'true'})
existing_clist_inherited = __salt__['zabbix.run_query'](c_def['qtype'] + '.get', q_params, **kwargs)
else:
existing_clist_inherited = []
if existing_clist_inherited:
return [c_all for c_all in existing_clist_all if c_all not in existing_clist_inherited]
return existing_clist_all
def _adjust_object_lists(obj):
'''
For creation or update of object that have attribute which contains a list Zabbix awaits plain list of IDs while
querying Zabbix for same object returns list of dicts
:param obj: Zabbix object parameters
'''
for subcomp in TEMPLATE_COMPONENT_DEF:
if subcomp in obj and TEMPLATE_COMPONENT_DEF[subcomp]['adjust']:
obj[subcomp] = [item[TEMPLATE_COMPONENT_DEF[subcomp]['qidname']] for item in obj[subcomp]]
def _manage_component(component, parent_id, defined, existing, template_id=None, **kwargs):
'''
Takes particular component list, compares it with existing, call appropriate API methods - create, update, delete.
:param component: component name
:param parent_id: ID of parent entity under which component should be created
:param defined: list of defined items of named component
:param existing: list of existing items of named component
:param template_id: In case that component need also template ID for creation (although parent_id is given?!?!?)
'''
zabbix_id_mapper = __salt__['zabbix.get_zabbix_id_mapper']()
dry_run = __opts__['test']
c_def = TEMPLATE_COMPONENT_DEF[component]
compare_key = c_def['filter']
defined_set = set([item[compare_key] for item in defined])
existing_set = set([item[compare_key] for item in existing])
create_set = defined_set - existing_set
update_set = defined_set & existing_set
delete_set = existing_set - defined_set
create_list = [item for item in defined if item[compare_key] in create_set]
for object_params in create_list:
if parent_id:
object_params.update({c_def['pid_ref_name']: parent_id})
if 'pid_ref_name2' in c_def:
object_params.update({c_def['pid_ref_name2']: template_id})
_adjust_object_lists(object_params)
if not dry_run:
object_create = __salt__['zabbix.run_query'](c_def['qtype'] + '.create', object_params, **kwargs)
if object_create:
object_ids = object_create[c_def['res_id_name']]
CHANGE_STACK.append({'component': component, 'action': 'create', 'params': object_params,
c_def['filter']: object_params[c_def['filter']], 'object_id': object_ids})
else:
CHANGE_STACK.append({'component': component, 'action': 'create', 'params': object_params,
'object_id': 'CREATED '+TEMPLATE_COMPONENT_DEF[component]['qtype']+' ID'})
delete_list = [item for item in existing if item[compare_key] in delete_set]
for object_del in delete_list:
object_id_name = zabbix_id_mapper[c_def['qtype']]
CHANGE_STACK.append({'component': component, 'action': 'delete', 'params': [object_del[object_id_name]]})
if not dry_run:
__salt__['zabbix.run_query'](c_def['qtype'] + '.delete', [object_del[object_id_name]], **kwargs)
for object_name in update_set:
ditem = next((item for item in defined if item[compare_key] == object_name), None)
eitem = next((item for item in existing if item[compare_key] == object_name), None)
diff_params = __salt__['zabbix.compare_params'](ditem, eitem, True)
if diff_params['new']:
diff_params['new'][zabbix_id_mapper[c_def['qtype']]] = eitem[zabbix_id_mapper[c_def['qtype']]]
diff_params['old'][zabbix_id_mapper[c_def['qtype']]] = eitem[zabbix_id_mapper[c_def['qtype']]]
_adjust_object_lists(diff_params['new'])
_adjust_object_lists(diff_params['old'])
CHANGE_STACK.append({'component': component, 'action': 'update', 'params': diff_params['new']})
if not dry_run:
__salt__['zabbix.run_query'](c_def['qtype'] + '.update', diff_params['new'], **kwargs)
# pylint: disable=too-many-statements,too-many-locals
def present(name, params, static_host_list=True, **kwargs):
'''
Creates Zabbix Template object or if differs update it according defined parameters. See Zabbix API documentation.
Zabbix API version: >3.0
:param name: Zabbix Template name
:param params: Additional parameters according to Zabbix API documentation
:param static_host_list: If hosts assigned to the template are controlled only by this state or can be also
assigned externally
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
.. note::
If there is a need to get a value from current zabbix online (e.g. ids of host groups you want the template
to be associated with), put a dictionary with two keys "query_object" and "query_name" instead of the value.
In this example we want to create template named "Testing Template", assign it to hostgroup Templates,
link it to two ceph nodes and create a macro.
.. note::
IMPORTANT NOTE:
Objects (except for template name) are identified by name (or by other key in some exceptional cases)
so changing name of object means deleting old one and creating new one with new ID !!!
.. note::
NOT SUPPORTED FEATURES:
- linked templates
- trigger dependencies
- groups and group prototypes for host prototypes
SLS Example:
.. code-block:: yaml
zabbix-template-present:
zabbix_template.present:
- name: Testing Template
# Do not touch existing assigned hosts
# True will detach all other hosts than defined here
- static_host_list: False
- params:
description: Template for Ceph nodes
groups:
# groups must already exist
# template must be at least in one hostgroup
- groupid:
query_object: hostgroup
query_name: Templates
macros:
- macro: "{$CEPH_CLUSTER_NAME}"
value: ceph
hosts:
# hosts must already exist
- hostid:
query_object: host
query_name: ceph-osd-01
- hostid:
query_object: host
query_name: ceph-osd-02
# templates:
# Linked templates - not supported by state module but can be linked manually (will not be touched)
applications:
- name: Ceph OSD
items:
- name: Ceph OSD avg fill item
key_: ceph.osd_avg_fill
type: 2
value_type: 0
delay: 60
units: '%'
description: 'Average fill of OSD'
applications:
- applicationid:
query_object: application
query_name: Ceph OSD
triggers:
- description: "Ceph OSD filled more that 90%"
expression: "{{'{'}}Testing Template:ceph.osd_avg_fill.last(){{'}'}}>90"
priority: 4
discoveries:
- name: Mounted filesystem discovery
key_: vfs.fs.discovery
type: 0
delay: 60
itemprototypes:
- name: Free disk space on {{'{#'}}FSNAME}
key_: vfs.fs.size[{{'{#'}}FSNAME},free]
type: 0
value_type: 3
delay: 60
applications:
- applicationid:
query_object: application
query_name: Ceph OSD
triggerprototypes:
- description: "Free disk space is less than 20% on volume {{'{#'}}FSNAME{{'}'}}"
expression: "{{'{'}}Testing Template:vfs.fs.size[{{'{#'}}FSNAME},free].last(){{'}'}}<20"
graphs:
- name: Ceph OSD avg fill graph
width: 900
height: 200
graphtype: 0
gitems:
- color: F63100
itemid:
query_object: item
query_name: Ceph OSD avg fill item
screens:
- name: Ceph
hsize: 1
vsize: 1
screenitems:
- x: 0
y: 0
resourcetype: 0
resourceid:
query_object: graph
query_name: Ceph OSD avg fill graph
'''
zabbix_id_mapper = __salt__['zabbix.get_zabbix_id_mapper']()
dry_run = __opts__['test']
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
params['host'] = name
# Divide template yaml definition into parts
# - template definition itself
# - simple template components
# - components that have other sub-components
# (e.g. discoveries - where parent ID is needed in advance for sub-component manipulation)
template_definition = {}
template_components = {}
discovery_components = []
for attr in params:
if attr in TEMPLATE_COMPONENT_ORDER and six.text_type(attr) != 'discoveries':
template_components[attr] = params[attr]
elif six.text_type(attr) == 'discoveries':
d_rules = []
for d_rule in params[attr]:
d_rule_components = {'query_pid': {'component': attr,
'filter_val': d_rule[TEMPLATE_COMPONENT_DEF[attr]['filter']]}}
for proto_name in DISCOVERYRULE_COMPONENT_ORDER:
if proto_name in d_rule:
d_rule_components[proto_name] = d_rule[proto_name]
del d_rule[proto_name]
discovery_components.append(d_rule_components)
d_rules.append(d_rule)
template_components[attr] = d_rules
else:
template_definition[attr] = params[attr]
# if a component is not defined, it means to remove existing items during update (empty list)
for attr in TEMPLATE_COMPONENT_ORDER:
if attr not in template_components:
template_components[attr] = []
# if a component is not defined, it means to remove existing items during update (empty list)
for attr in TEMPLATE_RELATIONS:
template_definition[attr] = params[attr] if attr in params and params[attr] else []
defined_obj = __salt__['zabbix.substitute_params'](template_definition, **kwargs)
log.info('SUBSTITUTED template_definition: %s', six.text_type(json.dumps(defined_obj, indent=4)))
tmpl_get = __salt__['zabbix.run_query']('template.get',
{'output': 'extend', 'selectGroups': 'groupid', 'selectHosts': 'hostid',
'selectTemplates': 'templateid', 'selectMacros': 'extend',
'filter': {'host': name}},
**kwargs)
log.info('TEMPLATE get result: %s', six.text_type(json.dumps(tmpl_get, indent=4)))
existing_obj = __salt__['zabbix.substitute_params'](tmpl_get[0], **kwargs) \
if tmpl_get and len(tmpl_get) == 1 else False
if existing_obj:
template_id = existing_obj[zabbix_id_mapper['template']]
if not static_host_list:
# Prepare objects for comparison
defined_wo_hosts = defined_obj
if 'hosts' in defined_obj:
defined_hosts = defined_obj['hosts']
del defined_wo_hosts['hosts']
else:
defined_hosts = []
existing_wo_hosts = existing_obj
if 'hosts' in existing_obj:
existing_hosts = existing_obj['hosts']
del existing_wo_hosts['hosts']
else:
existing_hosts = []
# Compare host list separately from the rest of the object comparison since the merged list is needed for
# update
hosts_list = _diff_and_merge_host_list(defined_hosts, existing_hosts)
# Compare objects without hosts
diff_params = __salt__['zabbix.compare_params'](defined_wo_hosts, existing_wo_hosts, True)
# Merge comparison results together
if ('new' in diff_params and 'hosts' in diff_params['new']) or hosts_list:
diff_params['new']['hosts'] = hosts_list
else:
diff_params = __salt__['zabbix.compare_params'](defined_obj, existing_obj, True)
if diff_params['new']:
diff_params['new'][zabbix_id_mapper['template']] = template_id
diff_params['old'][zabbix_id_mapper['template']] = template_id
log.info('TEMPLATE: update params: %s', six.text_type(json.dumps(diff_params, indent=4)))
CHANGE_STACK.append({'component': 'template', 'action': 'update', 'params': diff_params['new']})
if not dry_run:
tmpl_update = __salt__['zabbix.run_query']('template.update', diff_params['new'], **kwargs)
log.info('TEMPLATE update result: %s', six.text_type(tmpl_update))
else:
CHANGE_STACK.append({'component': 'template', 'action': 'create', 'params': defined_obj})
if not dry_run:
tmpl_create = __salt__['zabbix.run_query']('template.create', defined_obj, **kwargs)
log.info('TEMPLATE create result: ' + six.text_type(tmpl_create))
if tmpl_create:
template_id = tmpl_create['templateids'][0]
log.info('\n\ntemplate_components: %s', json.dumps(template_components, indent=4))
log.info('\n\ndiscovery_components: %s', json.dumps(discovery_components, indent=4))
log.info('\n\nCurrent CHANGE_STACK: %s', six.text_type(json.dumps(CHANGE_STACK, indent=4)))
if existing_obj or not dry_run:
for component in TEMPLATE_COMPONENT_ORDER:
log.info('\n\n\n\n\nCOMPONENT: %s\n\n', six.text_type(json.dumps(component)))
# 1) query for components which belongs to the template
existing_c_list = _get_existing_template_c_list(component, template_id, **kwargs)
existing_c_list_subs = __salt__['zabbix.substitute_params'](existing_c_list, **kwargs) \
if existing_c_list else []
if component in template_components:
defined_c_list_subs = __salt__['zabbix.substitute_params'](
template_components[component],
extend_params={TEMPLATE_COMPONENT_DEF[component]['qselectpid']: template_id},
filter_key=TEMPLATE_COMPONENT_DEF[component]['filter'],
**kwargs)
else:
defined_c_list_subs = []
# 2) take lists of particular component and compare -> do create, update and delete actions
_manage_component(component, template_id, defined_c_list_subs, existing_c_list_subs, **kwargs)
log.info('\n\nCurrent CHANGE_STACK: %s', six.text_type(json.dumps(CHANGE_STACK, indent=4)))
for d_rule_component in discovery_components:
# query for parent id -> "query_pid": {"filter_val": "vfs.fs.discovery", "component": "discoveries"}
q_def = d_rule_component['query_pid']
c_def = TEMPLATE_COMPONENT_DEF[q_def['component']]
q_object = c_def['qtype']
q_params = dict(c_def['output'])
q_params.update({c_def['qselectpid']: template_id})
q_params.update({'filter': {c_def['filter']: q_def['filter_val']}})
parent_id = __salt__['zabbix.get_object_id_by_params'](q_object, q_params, **kwargs)
for proto_name in DISCOVERYRULE_COMPONENT_ORDER:
log.info('\n\n\n\n\nPROTOTYPE_NAME: %s\n\n', six.text_type(json.dumps(proto_name)))
existing_p_list = _get_existing_template_c_list(proto_name, parent_id, **kwargs)
existing_p_list_subs = __salt__['zabbix.substitute_params'](existing_p_list, **kwargs)\
if existing_p_list else []
if proto_name in d_rule_component:
defined_p_list_subs = __salt__['zabbix.substitute_params'](
d_rule_component[proto_name],
extend_params={c_def['qselectpid']: template_id},
**kwargs)
else:
defined_p_list_subs = []
_manage_component(proto_name,
parent_id,
defined_p_list_subs,
existing_p_list_subs,
template_id=template_id,
**kwargs)
log.info('\n\nCurrent CHANGE_STACK: %s', six.text_type(json.dumps(CHANGE_STACK, indent=4)))
if not CHANGE_STACK:
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" already exists and corresponds to a definition.'.format(name)
else:
tmpl_action = next((item for item in CHANGE_STACK
if item['component'] == 'template' and item['action'] == 'create'), None)
if tmpl_action:
ret['result'] = True
if dry_run:
ret['comment'] = 'Zabbix Template "{0}" would be created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" does not exist.'.format(name),
'new': 'Zabbix Template "{0}" would be created '
'according definition.'.format(name)}}
else:
ret['comment'] = 'Zabbix Template "{0}" created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" did not exist.'.format(name),
'new': 'Zabbix Template "{0}" created according definition.'.format(name)}}
else:
ret['result'] = True
if dry_run:
ret['comment'] = 'Zabbix Template "{0}" would be updated.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" differs.'.format(name),
'new': 'Zabbix Template "{0}" would be updated '
'according definition.'.format(name)}}
else:
ret['comment'] = 'Zabbix Template "{0}" updated.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" differed.'.format(name),
'new': 'Zabbix Template "{0}" updated according definition.'.format(name)}}
return ret
def absent(name, **kwargs):
'''
Makes the Zabbix Template to be absent (either does not exist or delete it).
:param name: Zabbix Template name
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
.. code-block:: yaml
zabbix-template-absent:
zabbix_template.absent:
- name: Ceph OSD
'''
dry_run = __opts__['test']
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
try:
object_id = __salt__['zabbix.get_object_id_by_params']('template', {'filter': {'name': name}}, **kwargs)
except SaltException:
object_id = False
if not object_id:
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" does not exist.'.format(name)
else:
if dry_run:
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" would be deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" exists.'.format(name),
'new': 'Zabbix Template "{0}" would be deleted.'.format(name)}}
else:
tmpl_delete = __salt__['zabbix.run_query']('template.delete', [object_id], **kwargs)
if tmpl_delete:
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" existed.'.format(name),
'new': 'Zabbix Template "{0}" deleted.'.format(name)}}
return ret

View File

@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
'''
.. versionadded:: 2017.7
Management of Zabbix Valuemap object over Zabbix API.
:codeauthor: Jakub Sliva <jakub.sliva@ultimum.io>
'''
from __future__ import absolute_import
from __future__ import unicode_literals
import logging
import json
try:
from salt.ext import six
from salt.exceptions import SaltException
IMPORTS_OK = True
except ImportError:
IMPORTS_OK = False
log = logging.getLogger(__name__)
def __virtual__():
'''
Only make these states available if Zabbix module and run_query function is available
and all 3rd party modules imported.
'''
if 'zabbix.run_query' in __salt__ and IMPORTS_OK:
return True
return False, 'Import zabbix or other needed modules failed.'
def present(name, params, **kwargs):
'''
Creates Zabbix Value map object or if differs update it according defined parameters
:param name: Zabbix Value map name
:param params: Definition of the Zabbix Value map
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
.. code-block:: yaml
zabbix-valuemap-present:
zabbix_valuemap.present:
- name: Number mapping
- params:
mappings:
- value: 1
newvalue: one
- value: 2
newvalue: two
'''
zabbix_id_mapper = __salt__['zabbix.get_zabbix_id_mapper']()
dry_run = __opts__['test']
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
# Create input params substituting functions with their results
params['name'] = name
input_params = __salt__['zabbix.substitute_params'](params, **kwargs)
log.info('Zabbix Value map: input params: %s', six.text_type(json.dumps(input_params, indent=4)))
search = {'output': 'extend',
'selectMappings': 'extend',
'filter': {
'name': name
}}
# GET Value map object if exists
valuemap_get = __salt__['zabbix.run_query']('valuemap.get', search, **kwargs)
log.info('Zabbix Value map: valuemap.get result: %s', six.text_type(json.dumps(valuemap_get, indent=4)))
existing_obj = __salt__['zabbix.substitute_params'](valuemap_get[0], **kwargs) \
if valuemap_get and len(valuemap_get) == 1 else False
if existing_obj:
diff_params = __salt__['zabbix.compare_params'](input_params, existing_obj)
log.info('Zabbix Value map: input params: {%s', six.text_type(json.dumps(input_params, indent=4)))
log.info('Zabbix Value map: Object comparison result. Differences: %s', six.text_type(diff_params))
if diff_params:
diff_params[zabbix_id_mapper['valuemap']] = existing_obj[zabbix_id_mapper['valuemap']]
log.info('Zabbix Value map: update params: %s', six.text_type(json.dumps(diff_params, indent=4)))
if dry_run:
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" would be fixed.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" differs '
'in following parameters: {1}'.format(name, diff_params),
'new': 'Zabbix Value map "{0}" would correspond to definition.'.format(name)}}
else:
valuemap_update = __salt__['zabbix.run_query']('valuemap.update', diff_params, **kwargs)
log.info('Zabbix Value map: valuemap.update result: %s', six.text_type(valuemap_update))
if valuemap_update:
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" updated.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" differed '
'in following parameters: {1}'.format(name, diff_params),
'new': 'Zabbix Value map "{0}" fixed.'.format(name)}}
else:
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" already exists and corresponds to a definition.'.format(name)
else:
if dry_run:
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" would be created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" does not exist.'.format(name),
'new': 'Zabbix Value map "{0}" would be created '
'according definition.'.format(name)}}
else:
# ACTION.CREATE
valuemap_create = __salt__['zabbix.run_query']('valuemap.create', input_params, **kwargs)
log.info('Zabbix Value map: valuemap.create result: ' + six.text_type(valuemap_create))
if valuemap_create:
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" did not exist.'.format(name),
'new': 'Zabbix Value map "{0}" created according definition.'.format(name)}}
return ret
def absent(name, **kwargs):
'''
Makes the Zabbix Value map to be absent (either does not exist or delete it).
:param name: Zabbix Value map name
:param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
:param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
:param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
.. code-block:: yaml
zabbix-valuemap-absent:
zabbix_valuemap.absent:
- name: Value map name
'''
dry_run = __opts__['test']
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
try:
object_id = __salt__['zabbix.get_object_id_by_params']('valuemap', {'filter': {'name': name}}, **kwargs)
except SaltException:
object_id = False
if not object_id:
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" does not exist.'.format(name)
else:
if dry_run:
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" would be deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" exists.'.format(name),
'new': 'Zabbix Value map "{0}" would be deleted.'.format(name)}}
else:
valuemap_delete = __salt__['zabbix.run_query']('valuemap.delete', [object_id], **kwargs)
if valuemap_delete:
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" existed.'.format(name),
'new': 'Zabbix Value map "{0}" deleted.'.format(name)}}
return ret

View File

@ -3,18 +3,97 @@
:codeauthor: :email:`Christian McHugh <christian.mchugh@gmail.com>`
'''
# Import python libs
# Import Python Libs
from __future__ import absolute_import
from __future__ import unicode_literals
import salt.modules.zabbix as zabbix
# Import Salt Testing libs
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import skipIf, TestCase
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, patch
from tests.support.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
from salt.exceptions import SaltException
CONN_ARGS = {}
CONN_ARGS['url'] = 'http://test.url'
CONN_ARGS['auth'] = '1234'
GETID_QUERY_RESULT_OK = [{'internal': '0', 'flags': '0', 'groupid': '11', 'name': 'Databases'}]
GETID_QUERY_RESULT_BAD = [{'internal': '0', 'flags': '0', 'groupid': '11', 'name': 'Databases'}, {'another': 'object'}]
DEFINED_PARAMS = {'name': 'beta',
'eventsource': 2,
'status': 0,
'filter': {'evaltype': 2,
'conditions': [{'conditiontype': 24,
'operator': 2,
'value': 'db'}]},
'operations': [{'operationtype': 2},
{'operationtype': 4,
'opgroup': [{'groupid': {'query_object': 'hostgroup',
'query_name': 'Databases'}}]}],
'empty_list': []}
SUBSTITUTED_DEFINED_PARAMS = {'status': '0',
'filter': {'evaltype': '2',
'conditions': [{'operator': '2',
'conditiontype': '24',
'value': 'db'}]},
'eventsource': '2',
'name': 'beta',
'operations': [{'operationtype': '2'},
{'opgroup': [{'groupid': '11'}],
'operationtype': '4'}],
'empty_list': []}
EXISTING_OBJECT_PARAMS = {'status': '0',
'operations': [{'operationtype': '2', 'esc_period': '0', 'evaltype': '0', 'opconditions': [],
'esc_step_to': '1', 'actionid': '23', 'esc_step_from': '1',
'operationid': '64'},
{'operationtype': '4', 'esc_period': '0', 'evaltype': '0', 'opconditions': [],
'esc_step_to': '1', 'actionid': '23', 'esc_step_from': '1',
'opgroup': [{'groupid': '11',
'operationid': '65'}],
'operationid': '65'}],
'def_shortdata': '',
'name': 'beta',
'esc_period': '0',
'def_longdata': '',
'filter': {'formula': '',
'evaltype': '2',
'conditions': [{'operator': '2',
'conditiontype': '24',
'formulaid': 'A',
'value': 'DIFFERENT VALUE HERE'}],
'eval_formula': 'A'},
'eventsource': '2',
'actionid': '23',
'r_shortdata': '',
'r_longdata': '',
'recovery_msg': '0',
'empty_list': [{'dict_key': 'dic_val'}]}
DIFF_PARAMS_RESULT = {'filter': {'evaltype': '2',
'conditions': [{'operator': '2',
'conditiontype': '24',
'value': 'db'}]},
'empty_list': []}
DIFF_PARAMS_RESULT_WITH_ROLLBACK = {'new': DIFF_PARAMS_RESULT,
'old': {'filter': {'formula': '',
'evaltype': '2',
'conditions': [{'operator': '2',
'conditiontype': '24',
'formulaid': 'A',
'value': 'DIFFERENT VALUE HERE'}],
'eval_formula': 'A'},
'empty_list': [{'dict_key': 'dic_val'}]}}
@skipIf(NO_MOCK, NO_MOCK_REASON)
@ -26,6 +105,53 @@ class ZabbixTestCase(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self):
return {zabbix: {'__salt__': {'cmd.which_bin': lambda _: 'zabbix_server'}}}
def test_get_object_id_by_params(self):
'''
Test get_object_id function with expected result from API call
'''
with patch('salt.modules.zabbix.run_query', MagicMock(return_value=GETID_QUERY_RESULT_OK)):
self.assertEqual(zabbix.get_object_id_by_params('hostgroup', 'Databases'), '11')
def test_get_obj_id_by_params_fail(self):
'''
Test get_object_id function with unexpected result from API call
'''
with patch('salt.modules.zabbix.run_query', MagicMock(return_value=GETID_QUERY_RESULT_BAD)):
self.assertRaises(SaltException, zabbix.get_object_id_by_params, 'hostgroup', 'Databases')
def test_substitute_params(self):
'''
Test proper parameter substitution for defined input
'''
with patch('salt.modules.zabbix.get_object_id_by_params', MagicMock(return_value='11')):
self.assertEqual(zabbix.substitute_params(DEFINED_PARAMS), SUBSTITUTED_DEFINED_PARAMS)
def test_substitute_params_fail(self):
'''
Test proper parameter substitution if there is needed parameter missing
'''
self.assertRaises(SaltException, zabbix.substitute_params, {'groupid': {'query_object': 'hostgroup'}})
def test_compare_params(self):
'''
Test result comparison of two params structures
'''
self.assertEqual(zabbix.compare_params(SUBSTITUTED_DEFINED_PARAMS, EXISTING_OBJECT_PARAMS),
DIFF_PARAMS_RESULT)
def test_compare_params_rollback(self):
'''
Test result comparison of two params structures with rollback return value option
'''
self.assertEqual(zabbix.compare_params(SUBSTITUTED_DEFINED_PARAMS, EXISTING_OBJECT_PARAMS, True),
DIFF_PARAMS_RESULT_WITH_ROLLBACK)
def test_compare_params_fail(self):
'''
Test result comparison of two params structures where some data type mismatch exists
'''
self.assertRaises(SaltException, zabbix.compare_params, {'dict': 'val'}, {'dict': ['list']})
def test_apiiinfo_version(self):
'''
Test apiinfo_version

View File

@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Jakub Sliva <jakub.sliva@ultimum.io>`
'''
# Import Python Libs
from __future__ import absolute_import
from __future__ import unicode_literals
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
import salt.states.zabbix_action as zabbix_action
INPUT_PARAMS = {
'status': '0',
'filter': {'evaltype': '2', 'conditions': [{'operator': '2', 'conditiontype': '24', 'value': 'database'}]},
'eventsource': '2',
'name': 'Auto registration Databases',
'operations': [{'opgroup': [{'groupid': '6'}], 'operationtype': '4'}]}
EXISTING_OBJ = [{
'status': '0',
'operations': [{'operationtype': '4', 'esc_period': '0', 'evaltype': '0', 'opconditions': [],
'esc_step_to': '1', 'actionid': '28', 'esc_step_from': '1',
'opgroup': [{'groupid': '6', 'operationid': '92'}],
'operationid': '92'}],
'def_shortdata': '',
'name': 'Auto registration Databases',
'esc_period': '0',
'def_longdata': '',
'filter': {'formula': '', 'evaltype': '2', 'conditions': [{'operator': '2', 'conditiontype': '24',
'formulaid': 'A', 'value': 'database'}],
'eval_formula': 'A'},
'eventsource': '2',
'actionid': '28',
'r_shortdata': '',
'r_longdata': '',
'recovery_msg': '0'}]
EXISTING_OBJ_DIFF = {
'status': '0',
'operations': [{'operationtype': '4', 'esc_period': '0', 'evaltype': '0', 'opconditions': [],
'esc_step_to': '1', 'actionid': '28', 'esc_step_from': '1',
'opgroup': [{'groupid': '6', 'operationid': '92'}],
'operationid': '92'}],
'def_shortdata': '',
'name': 'Auto registration Databases',
'esc_period': '0',
'def_longdata': '',
'filter': {'formula': '', 'evaltype': '2', 'conditions': [{'operator': '2', 'conditiontype': '24',
'formulaid': 'A', 'value': 'SOME OTHER VALUE'}],
'eval_formula': 'A'},
'eventsource': '2',
'actionid': '28',
'r_shortdata': '',
'r_longdata': '',
'recovery_msg': '0'}
DIFF_PARAMS = {'filter': {'evaltype': '2',
'conditions': [{'operator': '2', 'conditiontype': '24', 'value': 'virtual'}]},
'actionid': '28'}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class ZabbixActionTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for salt.modules.zabbix
'''
def setup_loader_modules(self):
return {zabbix_action: {}}
def test_present_create(self):
'''
Test to ensure that named action is created
'''
name = 'Auto registration Databases'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
def side_effect_run_query(*args):
'''
Differentiate between __salt__ exec module function calls with different parameters.
'''
if args[0] == 'action.get':
return False
elif args[0] == 'action.create':
return True
with patch.dict(zabbix_action.__opts__, {'test': False}):
with patch.dict(zabbix_action.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'action': 'actionid'}),
'zabbix.substitute_params': MagicMock(side_effect=[INPUT_PARAMS, False]),
'zabbix.run_query': MagicMock(side_effect=side_effect_run_query),
'zabbix.compare_params': MagicMock(return_value={})}):
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" did not exist.'.format(name),
'new': 'Zabbix Action "{0}" created according definition.'.format(name)}}
self.assertDictEqual(zabbix_action.present(name, {}), ret)
def test_present_exists(self):
'''
Test to ensure that named action is present and not changed
'''
name = 'Auto registration Databases'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
with patch.dict(zabbix_action.__opts__, {'test': False}):
with patch.dict(zabbix_action.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'action': 'actionid'}),
'zabbix.substitute_params': MagicMock(side_effect=[INPUT_PARAMS, EXISTING_OBJ]),
'zabbix.run_query': MagicMock(return_value=['length of result is 1']),
'zabbix.compare_params': MagicMock(return_value={})}):
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" already exists and corresponds to a definition.'.format(name)
self.assertDictEqual(zabbix_action.present(name, {}), ret)
def test_present_update(self):
'''
Test to ensure that named action is present but must be updated
'''
name = 'Auto registration Databases'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
def side_effect_run_query(*args):
'''
Differentiate between __salt__ exec module function calls with different parameters.
'''
if args[0] == 'action.get':
return ['length of result is 1 = action exists']
elif args[0] == 'action.update':
return DIFF_PARAMS
with patch.dict(zabbix_action.__opts__, {'test': False}):
with patch.dict(zabbix_action.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'action': 'actionid'}),
'zabbix.substitute_params': MagicMock(side_effect=[INPUT_PARAMS, EXISTING_OBJ_DIFF]),
'zabbix.run_query': MagicMock(side_effect=side_effect_run_query),
'zabbix.compare_params': MagicMock(return_value=DIFF_PARAMS)}):
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" updated.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" differed '
'in following parameters: {1}'.format(name, DIFF_PARAMS),
'new': 'Zabbix Action "{0}" fixed.'.format(name)}}
self.assertDictEqual(zabbix_action.present(name, {}), ret)
def test_absent_test_mode(self):
'''
Test to ensure that named action is absent in test mode
'''
name = 'Auto registration Databases'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
with patch.dict(zabbix_action.__opts__, {'test': True}):
with patch.dict(zabbix_action.__salt__, {'zabbix.get_object_id_by_params': MagicMock(return_value=11)}):
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" would be deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" exists.'.format(name),
'new': 'Zabbix Action "{0}" would be deleted.'.format(name)}}
self.assertDictEqual(zabbix_action.absent(name), ret)
def test_absent(self):
'''
Test to ensure that named action is absent
'''
name = 'Auto registration Databases'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
with patch.dict(zabbix_action.__opts__, {'test': False}):
with patch.dict(zabbix_action.__salt__, {'zabbix.get_object_id_by_params': MagicMock(return_value=False)}):
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" does not exist.'.format(name)
self.assertDictEqual(zabbix_action.absent(name), ret)
with patch.dict(zabbix_action.__salt__, {'zabbix.get_object_id_by_params': MagicMock(return_value=11)}):
with patch.dict(zabbix_action.__salt__, {'zabbix.run_query': MagicMock(return_value=True)}):
ret['result'] = True
ret['comment'] = 'Zabbix Action "{0}" deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Action "{0}" existed.'.format(name),
'new': 'Zabbix Action "{0}" deleted.'.format(name)}}
self.assertDictEqual(zabbix_action.absent(name), ret)

View File

@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Jakub Sliva <jakub.sliva@ultimum.io>`
'''
# Import Python Libs
from __future__ import absolute_import
from __future__ import unicode_literals
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
import salt.states.zabbix_template as zabbix_template
INPUT_PARAMS = {"applications": [{"name": "Ceph OSD"}]}
DEFINED_OBJ = {"macros": [{"macro": "{$CEPH_CLUSTER_NAME}", "value": "ceph"}], "host": "A Testing Template",
"hosts": [{"hostid": "10112"}, {"hostid": "10113"}], "description": "Template for Ceph nodes",
"groups": [{"groupid": "1"}]}
DEFINED_C_LIST_SUBS = {"applications": [{"name": "Ceph OSD"}], 'graphs': [], 'triggers': [], 'items': [],
'httpTests': [], 'screens': [], 'gitems': [], 'discoveries': []}
SUBSTITUTE_PARAMS_CREATE = [DEFINED_OBJ, [], DEFINED_C_LIST_SUBS['applications'], [], [], [], [], [], [], [], []]
EXISTING_OBJ = [{"available": "0", "tls_connect": "1", "maintenance_type": "0", "groups": [{"groupid": "1"}],
"macros": [{"macro": "{$CEPH_CLUSTER_NAME}", "hostmacroid": "60", "hostid": "10206", "value": "ceph"}],
"hosts": [{"hostid": "10112"}, {"hostid": "10113"}], "status": "3",
"description": "Template for Ceph nodes", "host": "A Testing Template", "disable_until": "0",
"templateid": "10206", "name": "A Testing Template"}]
SUBSTITUTE_PARAMS_EXISTS = [DEFINED_OBJ, EXISTING_OBJ[0], [], [], [], [], [], [], [], []]
EXISTING_OBJ_DIFF = [{"groups": [{"groupid": "1"}], "macros": [{"macro": "{$CEPH_CLUSTER_NAME}", "hostmacroid": "60",
"hostid": "10206", "value": "ceph"}],
"hosts": [{"hostid": "10112"}, {"hostid": "10113"}], "status": "3", "templateid": "10206",
"name": "A Testing Template"}]
SUBSTITUTE_PARAMS_UPDATE = [DEFINED_OBJ, EXISTING_OBJ_DIFF[0], [], [], [], [], [], [], [], []]
DIFF_PARAMS = {'old': {}, 'new': {'macros': [], 'templateid': '10206'}}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class ZabbixTemplateTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for salt.modules.zabbix
'''
def setup_loader_modules(self):
return {zabbix_template: {}}
@patch('salt.states.zabbix_template.CHANGE_STACK', [])
def test_present_create(self):
'''
Test to ensure that named template is created
'''
name = 'A Testing Template'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
def side_effect_run_query(*args):
'''
Differentiate between __salt__ exec module function calls with different parameters.
'''
if args[0] == 'template.get':
return []
elif args[0] == 'template.create':
return {'templateids': ['10206']}
elif args[0] == 'application.get':
return []
elif args[0] == 'application.create':
return {"applicationids": ["701"]}
with patch.dict(zabbix_template.__opts__, {'test': False}):
with patch.dict(zabbix_template.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'template': 'templateid'}),
'zabbix.substitute_params': MagicMock(side_effect=SUBSTITUTE_PARAMS_CREATE),
'zabbix.run_query': MagicMock(side_effect=side_effect_run_query),
'zabbix.compare_params': MagicMock(return_value={})}):
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" did not exist.'.format(name),
'new': 'Zabbix Template "{0}" created according definition.'.format(name)}}
self.assertDictEqual(zabbix_template.present(name, {}), ret)
@patch('salt.states.zabbix_template.CHANGE_STACK', [])
def test_present_exists(self):
'''
Test to ensure that named template is present and not changed
'''
name = 'A Testing Template'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
def side_effect_run_query(*args):
'''
Differentiate between __salt__ exec module function calls with different parameters.
'''
if args[0] == 'template.get':
return EXISTING_OBJ
elif args[0] == 'application.get':
return ['non-empty']
with patch.dict(zabbix_template.__opts__, {'test': False}):
with patch.dict(zabbix_template.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'template': 'templateid'}),
'zabbix.substitute_params': MagicMock(side_effect=SUBSTITUTE_PARAMS_EXISTS),
'zabbix.run_query': MagicMock(side_effect=side_effect_run_query),
'zabbix.compare_params': MagicMock(return_value={'new': {}, 'old': {}})}):
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" already exists and corresponds to a definition.'.format(name)
self.assertDictEqual(zabbix_template.present(name, {}), ret)
@patch('salt.states.zabbix_template.CHANGE_STACK', [])
def test_present_update(self):
'''
Test to ensure that named template is present but must be updated
'''
name = 'A Testing Template'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
def side_effect_run_query(*args):
'''
Differentiate between __salt__ exec module function calls with different parameters.
'''
if args[0] == 'template.get':
return ['length of result is 1 = template exists']
elif args[0] == 'template.update':
return DIFF_PARAMS
with patch.dict(zabbix_template.__opts__, {'test': False}):
with patch.dict(zabbix_template.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'template': 'templateid'}),
'zabbix.substitute_params': MagicMock(side_effect=SUBSTITUTE_PARAMS_UPDATE),
'zabbix.run_query': MagicMock(side_effect=side_effect_run_query),
'zabbix.compare_params': MagicMock(return_value=DIFF_PARAMS)}):
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" updated.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" differed.'.format(name),
'new': 'Zabbix Template "{0}" updated according definition.'.format(name)}}
self.assertDictEqual(zabbix_template.present(name, {}), ret)
def test_absent_test_mode(self):
'''
Test to ensure that named template is absent in test mode
'''
name = 'A Testing Template'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
with patch.dict(zabbix_template.__opts__, {'test': True}):
with patch.dict(zabbix_template.__salt__, {'zabbix.get_object_id_by_params': MagicMock(return_value=11)}):
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" would be deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" exists.'.format(name),
'new': 'Zabbix Template "{0}" would be deleted.'.format(name)}}
self.assertDictEqual(zabbix_template.absent(name), ret)
def test_absent(self):
'''
Test to ensure that named template is absent
'''
name = 'A Testing Template'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
with patch.dict(zabbix_template.__opts__, {'test': False}):
with patch.dict(zabbix_template.__salt__,
{'zabbix.get_object_id_by_params': MagicMock(return_value=False)}):
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" does not exist.'.format(name)
self.assertDictEqual(zabbix_template.absent(name), ret)
with patch.dict(zabbix_template.__salt__, {'zabbix.get_object_id_by_params': MagicMock(return_value=11)}):
with patch.dict(zabbix_template.__salt__, {'zabbix.run_query': MagicMock(return_value=True)}):
ret['result'] = True
ret['comment'] = 'Zabbix Template "{0}" deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Template "{0}" existed.'.format(name),
'new': 'Zabbix Template "{0}" deleted.'.format(name)}}
self.assertDictEqual(zabbix_template.absent(name), ret)

View File

@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Jakub Sliva <jakub.sliva@ultimum.io>`
'''
# Import Python Libs
from __future__ import absolute_import
from __future__ import unicode_literals
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
import salt.states.zabbix_valuemap as zabbix_valuemap
INPUT_PARAMS = {'mappings': [{'newvalue': 'OK', 'value': '0h'}, {'newvalue': 'Failure', 'value': '1'}],
'name': 'Server HP Health'}
EXISTING_OBJ = [{'valuemapid': '21', 'name': 'Server HP Health', 'mappings': [{'newvalue': 'OK', 'value': '0h'},
{'newvalue': 'Failure', 'value': '1'}]}]
EXISTING_OBJ_DIFF = {'valuemapid': '21', 'name': 'Server HP Health', 'mappings': [{'newvalue': 'OK', 'value': '0h'},
{'newvalue': 'Failure', 'value': '1'},
{'newvalue': 'some', 'value': '2'}]}
DIFF_PARAMS = {'valuemapid': '21', 'mappings': [{'newvalue': 'OK', 'value': '0h'},
{'newvalue': 'Failure', 'value': '1'}]}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class ZabbixActionTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for salt.modules.zabbix
'''
def setup_loader_modules(self):
return {zabbix_valuemap: {}}
def test_present_create(self):
'''
Test to ensure that named value map is created
'''
name = 'Server HP Health'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
def side_effect_run_query(*args):
'''
Differentiate between __salt__ exec module function calls with different parameters.
'''
if args[0] == 'valuemap.get':
return False
elif args[0] == 'valuemap.create':
return True
with patch.dict(zabbix_valuemap.__opts__, {'test': False}):
with patch.dict(zabbix_valuemap.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'valuemap': 'valuemapid'}),
'zabbix.substitute_params': MagicMock(side_effect=[INPUT_PARAMS, False]),
'zabbix.run_query': MagicMock(side_effect=side_effect_run_query),
'zabbix.compare_params': MagicMock(return_value={})}):
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" created.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" did not exist.'.format(name),
'new': 'Zabbix Value map "{0}" created according definition.'.format(name)}}
self.assertDictEqual(zabbix_valuemap.present(name, {}), ret)
def test_present_exists(self):
'''
Test to ensure that named value map is present and not changed
'''
name = 'Server HP Health'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
with patch.dict(zabbix_valuemap.__opts__, {'test': False}):
with patch.dict(zabbix_valuemap.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'valuemap': 'valuemapid'}),
'zabbix.substitute_params': MagicMock(side_effect=[INPUT_PARAMS, EXISTING_OBJ]),
'zabbix.run_query': MagicMock(return_value=['length of result is 1']),
'zabbix.compare_params': MagicMock(return_value={})}):
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" already exists and corresponds to a definition.'.format(name)
self.assertDictEqual(zabbix_valuemap.present(name, {}), ret)
def test_present_update(self):
'''
Test to ensure that named value map is present but must be updated
'''
name = 'Server HP Health'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
def side_effect_run_query(*args):
'''
Differentiate between __salt__ exec module function calls with different parameters.
'''
if args[0] == 'valuemap.get':
return ['length of result is 1 = valuemap exists']
elif args[0] == 'valuemap.update':
return DIFF_PARAMS
with patch.dict(zabbix_valuemap.__opts__, {'test': False}):
with patch.dict(zabbix_valuemap.__salt__,
{'zabbix.get_zabbix_id_mapper': MagicMock(return_value={'valuemap': 'valuemapid'}),
'zabbix.substitute_params': MagicMock(side_effect=[INPUT_PARAMS, EXISTING_OBJ_DIFF]),
'zabbix.run_query': MagicMock(side_effect=side_effect_run_query),
'zabbix.compare_params': MagicMock(return_value=DIFF_PARAMS)}):
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" updated.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" differed '
'in following parameters: {1}'.format(name, DIFF_PARAMS),
'new': 'Zabbix Value map "{0}" fixed.'.format(name)}}
self.assertDictEqual(zabbix_valuemap.present(name, {}), ret)
def test_absent_test_mode(self):
'''
Test to ensure that named value map is absent in test mode
'''
name = 'Server HP Health'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
with patch.dict(zabbix_valuemap.__opts__, {'test': True}):
with patch.dict(zabbix_valuemap.__salt__, {'zabbix.get_object_id_by_params': MagicMock(return_value=11)}):
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" would be deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" exists.'.format(name),
'new': 'Zabbix Value map "{0}" would be deleted.'.format(name)}}
self.assertDictEqual(zabbix_valuemap.absent(name), ret)
def test_absent(self):
'''
Test to ensure that named value map is absent
'''
name = 'Server HP Health'
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
with patch.dict(zabbix_valuemap.__opts__, {'test': False}):
with patch.dict(zabbix_valuemap.__salt__,
{'zabbix.get_object_id_by_params': MagicMock(return_value=False)}):
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" does not exist.'.format(name)
self.assertDictEqual(zabbix_valuemap.absent(name), ret)
with patch.dict(zabbix_valuemap.__salt__, {'zabbix.get_object_id_by_params': MagicMock(return_value=11)}):
with patch.dict(zabbix_valuemap.__salt__, {'zabbix.run_query': MagicMock(return_value=True)}):
ret['result'] = True
ret['comment'] = 'Zabbix Value map "{0}" deleted.'.format(name)
ret['changes'] = {name: {'old': 'Zabbix Value map "{0}" existed.'.format(name),
'new': 'Zabbix Value map "{0}" deleted.'.format(name)}}
self.assertDictEqual(zabbix_valuemap.absent(name), ret)