Add zenoss state and execution modules

This change adds support for the Zenoss monitoring tool.

The execution module provides support for adding a new device and checking if a device is already in Zenoss.

The state module provides the abililty to set a state of 'monitored' which requires that a device be added to Zenoss.

Tested with Zenoss 4.2.5 Enterprise and likely will work with all in the 4.x series including Core.
This commit is contained in:
Seth Miller 2015-07-22 14:57:28 -05:00
parent 5c8994db74
commit 95c0935b06
6 changed files with 245 additions and 0 deletions

View File

@ -333,6 +333,7 @@ Full list of builtin execution modules
xmpp
yumpkg
zcbuildout
zenoss
zfs
zk_concurrency
znc

View File

@ -0,0 +1,6 @@
===================
salt.modules.zenoss
===================
.. automodule:: salt.modules.zenoss
:members:

View File

@ -192,4 +192,5 @@ Full list of builtin state modules
x509
xmpp
zcbuildout
zenoss
zk_concurrency

View File

@ -0,0 +1,6 @@
==================
salt.states.zenoss
==================
.. automodule:: salt.states.zenoss
:members:

160
salt/modules/zenoss.py Normal file
View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
'''
Module for working with the Zenoss API
:configuration: This module requires a 'zenoss' entry in the master/minion config.
For example:
.. code-block:: yaml
zenoss:
hostname: https://zenoss.example.com
username: admin
password: admin123
'''
from __future__ import absolute_import
import re
import json
import logging
try:
import requests
HAS_LIBS = True
except ImportError:
HAS_LIBS = False
# Disable INFO level logs from requests/urllib3
urllib3_logger = logging.getLogger('urllib3')
urllib3_logger.setLevel(logging.WARNING)
log = logging.getLogger(__name__)
def __virtual__():
'''
Only load if requests is installed
'''
if HAS_LIBS:
return 'zenoss'
ROUTERS = {'MessagingRouter': 'messaging',
'EventsRouter': 'evconsole',
'ProcessRouter': 'process',
'ServiceRouter': 'service',
'DeviceRouter': 'device',
'NetworkRouter': 'network',
'TemplateRouter': 'template',
'DetailNavRouter': 'detailnav',
'ReportRouter': 'report',
'MibRouter': 'mib',
'ZenPackRouter': 'zenpack'}
def _session():
'''
Create a session to be used when connecting to Zenoss.
'''
config = __salt__['config.option']('zenoss')
session = requests.session()
session.auth = (config.get('username'), config.get('password'))
session.verify = False
session.headers.update({'Content-type': 'application/json; charset=utf-8'})
return session
def _router_request(router, method, data=None):
'''
Make a request to the Zenoss API router
'''
if router not in ROUTERS:
return False
req_data = json.dumps([dict(
action=router,
method=method,
data=data,
type='rpc',
tid=1)])
config = __salt__['config.option']('zenoss')
log.debug('Making request to router %s with method %s', router, method)
url = '{0}/zport/dmd/{1}_router'.format(config.get('hostname'), ROUTERS[router])
response = _session().post(url, data=req_data)
# The API returns a 200 response code even whe auth is bad.
# With bad auth, the login page is displayed. Here I search for
# an element on the login form to determine if auth failed.
if re.search('name="__ac_name"', response.content):
log.error('Request failed. Bad username/password.')
raise Exception('Request failed. Bad username/password.')
return json.loads(response.content).get('result', None)
def _determine_device_class():
'''
If no device class is given when adding a device, this helps determine
'''
if __salt__['grains.get']('kernel') == 'Linux':
return '/Server/Linux'
def _find_device(device):
'''
Find a device in Zenoss. If device not found, returns None.
'''
data = [{'uid': '/zport/dmd/Devices', 'params': {}, 'limit': None}]
all_devices = _router_request('DeviceRouter', 'getDevices', data=data)
for dev in all_devices['devices']:
if dev['name'] == device:
# We need to save the has for later operations
dev['hash'] = all_devices['hash']
log.info('Found device %s in Zenoss', device)
return dev
log.info('Unable to find device %s in Zenoss', device)
return None
def device_exists(device=None):
'''
Check to see if a device already exists in Zenoss.
Parameters:
device: (Optional) Will use the grain 'fqdn' by default
CLI Example:
salt '*' zenoss.device_exists
'''
if not device:
device = __salt__['grains.get']('fqdn')
if _find_device(device):
return True
return False
def add_device(device=None, device_class=None, collector='localhost'):
'''
A function to connect to a zenoss server and add a new device entry.
Parameters:
device: (Optional) Will use the grain 'fqdn' by default.
device_class: (Optional) The device class to use. If none, will determine based on kernel grain.
collector: (Optional) The collector to use for this device. Defaults to 'localhost'.
CLI Example:
salt '*' zenoss.add_device
'''
if not device:
device = __salt__['grains.get']('fqdn')
if not device_class:
device_class = _determine_device_class()
log.info('Adding device %s to zenoss', device)
data = dict(deviceName=device, deviceClass=device_class, model=True, collector=collector)
response = _router_request('DeviceRouter', 'addDevice', data=[data])
return response

71
salt/states/zenoss.py Normal file
View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
'''
State to manage monitoring in Zenoss.
This state module depends on the 'zenoss' Salt execution module.
Allows for setting a state of minions in Zenoss using the Zenoss API. Currently Zenoss 4.x is supported.
.. code-block:: yaml
enable_monitoring:
zenoss.monitored:
- name: web01.example.com
- device_class: /Servers/Linux
- collector: localhost
'''
import time
import logging
from __future__ import absolute_import
log = logging.getLogger(__name__)
def __virtual__():
'''
Only load if the Zenoss execution module is available.
'''
if 'zenoss.add_device' in __salt__:
return 'zenoss'
def monitored(name, device_class=None, collector='localhost'):
'''
Ensure a device is monitored. The 'name' given will be used for Zenoss device name and should be resolvable.
.. code-block:: yaml
enable_monitoring:
zenoss.monitored:
- name: web01.example.com
- device_class: /Servers/Linux
- collector: localhost
'''
ret = {}
ret['name'] = name
# If device is already monitored, return early
if __salt__['zenoss.device_exists'](name):
ret['result'] = True
ret['changes'] = None
ret['comment'] = '{0} is already monitored'.format(name)
return ret
if __opts__['test'] == True:
ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
ret['changes'] = {'old': 'monitored == False', 'new': 'monitored == True'}
ret['result'] = None
return ret
# Device not yet in Zenoss. Add and check result
if __salt__['zenoss.add_device'](name, device_class, collector):
ret['result'] = True
ret['changes'] = {'old': 'monitored == False', 'new': 'monitored == True'}
ret['comment'] = '{0} has been added to Zenoss'.format(name)
else:
ret['result'] = False
ret['changes'] = None
ret['comment'] = 'Unable to add {0} to Zenoss'.format(name)
return ret