mirror of
https://github.com/valitydev/salt.git
synced 2024-11-08 17:33:54 +00:00
Merge branch 'develop' into comspec
This commit is contained in:
commit
ed8f6ed353
@ -22,6 +22,7 @@ beacon modules
|
||||
load
|
||||
log
|
||||
memusage
|
||||
napalm_beacon
|
||||
network_info
|
||||
network_settings
|
||||
pkg
|
||||
|
6
doc/ref/beacons/all/salt.beacons.napalm_beacon.rst
Normal file
6
doc/ref/beacons/all/salt.beacons.napalm_beacon.rst
Normal file
@ -0,0 +1,6 @@
|
||||
==========================
|
||||
salt.beacons.napalm_beacon
|
||||
==========================
|
||||
|
||||
.. automodule:: salt.beacons.napalm_beacon
|
||||
:members:
|
353
salt/beacons/napalm_beacon.py
Normal file
353
salt/beacons/napalm_beacon.py
Normal file
@ -0,0 +1,353 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
NAPALM functions
|
||||
================
|
||||
|
||||
.. versionadded:: Oxygen
|
||||
|
||||
Watch NAPALM functions and fire events on specific triggers.
|
||||
|
||||
.. note::
|
||||
|
||||
The ``NAPALM`` beacon only works only when running under
|
||||
a regular Minion or a Proxy Minion, managed via NAPALM_.
|
||||
Check the documentation for the
|
||||
:mod:`NAPALM proxy module <salt.proxy.napalm>`.
|
||||
|
||||
_NAPALM: http://napalm.readthedocs.io/en/latest/index.html
|
||||
|
||||
The configuration accepts a list of Salt functions to be
|
||||
invoked, and the corresponding output hierarchy that should
|
||||
be matched against. To invoke a function with certain
|
||||
arguments, they can be specified using the ``_args`` key, or
|
||||
``_kwargs`` for more specific key-value arguments.
|
||||
|
||||
The match structure follows the output hierarchy of the NAPALM
|
||||
functions, under the ``out`` key.
|
||||
|
||||
For example, the following is normal structure returned by the
|
||||
:mod:`ntp.stats <salt.modules.napalm_ntp.stats>` execution function:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"comment": "",
|
||||
"result": true,
|
||||
"out": [
|
||||
{
|
||||
"referenceid": ".GPSs.",
|
||||
"remote": "172.17.17.1",
|
||||
"synchronized": true,
|
||||
"reachability": 377,
|
||||
"offset": 0.461,
|
||||
"when": "860",
|
||||
"delay": 143.606,
|
||||
"hostpoll": 1024,
|
||||
"stratum": 1,
|
||||
"jitter": 0.027,
|
||||
"type": "-"
|
||||
},
|
||||
{
|
||||
"referenceid": ".INIT.",
|
||||
"remote": "172.17.17.2",
|
||||
"synchronized": false,
|
||||
"reachability": 0,
|
||||
"offset": 0.0,
|
||||
"when": "-",
|
||||
"delay": 0.0,
|
||||
"hostpoll": 1024,
|
||||
"stratum": 16,
|
||||
"jitter": 4000.0,
|
||||
"type": "-"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
In order to fire events when the synchronization is lost with
|
||||
one of the NTP peers, e.g., ``172.17.17.2``, we can match it explicitly as:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
ntp.stats:
|
||||
remote: 172.17.17.2
|
||||
synchronized: false
|
||||
|
||||
There is one single nesting level, as the output of ``ntp.stats`` is
|
||||
just a list of dictionaries, and this beacon will compare each dictionary
|
||||
from the list with the structure examplified above.
|
||||
|
||||
.. note::
|
||||
|
||||
When we want to match on any element at a certain level, we can
|
||||
configure ``*`` to match anything.
|
||||
|
||||
Considering a more complex structure consisting on multiple nested levels,
|
||||
e.g., the output of the :mod:`bgp.neighbors <salt.modules.napalm_bgp.neighbors>`
|
||||
execution function, to check when any neighbor from the ``global``
|
||||
routing table is down, the match structure would have the format:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
bgp.neighbors:
|
||||
global:
|
||||
'*':
|
||||
up: false
|
||||
|
||||
The match structure above will match any BGP neighbor, with
|
||||
any network (``*`` matches any AS number), under the ``global`` VRF.
|
||||
In other words, this beacon will push an event on the Salt bus
|
||||
when there's a BGP neighbor down.
|
||||
|
||||
The right operand can also accept mathematical operations
|
||||
(i.e., ``<``, ``<=``, ``!=``, ``>``, ``>=`` etc.) when comparing
|
||||
numerical values.
|
||||
|
||||
Configuration Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
beacons:
|
||||
napalm:
|
||||
- net.interfaces:
|
||||
# fire events when any interfaces is down
|
||||
'*':
|
||||
is_up: false
|
||||
- net.interfaces:
|
||||
# fire events only when the xe-0/0/0 interface is down
|
||||
'xe-0/0/0':
|
||||
is_up: false
|
||||
- ntp.stats:
|
||||
# fire when there's any NTP peer unsynchornized
|
||||
synchronized: false
|
||||
- ntp.stats:
|
||||
# fire only when the synchronization
|
||||
# with with the 172.17.17.2 NTP server is lost
|
||||
_args:
|
||||
- 172.17.17.2
|
||||
synchronized: false
|
||||
- ntp.stats:
|
||||
# fire only when there's a NTP peer with
|
||||
# synchronization stratum > 5
|
||||
stratum: '> 5'
|
||||
|
||||
Event structure example:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
salt/beacon/edge01.bjm01/napalm/junos/ntp.stats {
|
||||
"_stamp": "2017-09-05T09:51:09.377202",
|
||||
"args": [],
|
||||
"data": {
|
||||
"comment": "",
|
||||
"out": [
|
||||
{
|
||||
"delay": 0.0,
|
||||
"hostpoll": 1024,
|
||||
"jitter": 4000.0,
|
||||
"offset": 0.0,
|
||||
"reachability": 0,
|
||||
"referenceid": ".INIT.",
|
||||
"remote": "172.17.17.1",
|
||||
"stratum": 16,
|
||||
"synchronized": false,
|
||||
"type": "-",
|
||||
"when": "-"
|
||||
}
|
||||
],
|
||||
"result": true
|
||||
},
|
||||
"fun": "ntp.stats",
|
||||
"id": "edge01.bjm01",
|
||||
"kwargs": {},
|
||||
"match": {
|
||||
"stratum": "> 5"
|
||||
}
|
||||
}
|
||||
|
||||
The event examplified above has been fired when the device
|
||||
identified by the Minion id ``edge01.bjm01`` has been synchronized
|
||||
with a NTP server at a stratum level greater than 5.
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import Python std lib
|
||||
import re
|
||||
import logging
|
||||
|
||||
# Import Salt modules
|
||||
from salt.ext import six
|
||||
import salt.utils.napalm
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
_numeric_regex = re.compile(r'^(<|>|<=|>=|==|!=)\s*(\d+(\.\d+){0,1})$')
|
||||
# the numeric regex will match the right operand, e.g '>= 20', '< 100', '!= 20', '< 1000.12' etc.
|
||||
_numeric_operand = {
|
||||
'<': '__lt__',
|
||||
'>': '__gt__',
|
||||
'>=': '__ge__',
|
||||
'<=': '__le__',
|
||||
'==': '__eq__',
|
||||
'!=': '__ne__',
|
||||
} # mathematical operand - private method map
|
||||
|
||||
|
||||
__virtualname__ = 'napalm'
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
This beacon can only work when running under a regular or a proxy minion, managed through napalm.
|
||||
'''
|
||||
return salt.utils.napalm.virtual(__opts__, __virtualname__, __file__)
|
||||
|
||||
|
||||
def _compare(cur_cmp, cur_struct):
|
||||
'''
|
||||
Compares two objects and return a boolean value
|
||||
when there's a match.
|
||||
'''
|
||||
if isinstance(cur_cmp, dict) and isinstance(cur_struct, dict):
|
||||
log.debug('Comparing dict to dict')
|
||||
for cmp_key, cmp_value in six.iteritems(cur_cmp):
|
||||
if cmp_key == '*':
|
||||
# matches any key from the source dictionary
|
||||
if isinstance(cmp_value, dict):
|
||||
found = False
|
||||
for _, cur_struct_val in six.iteritems(cur_struct):
|
||||
found |= _compare(cmp_value, cur_struct_val)
|
||||
return found
|
||||
else:
|
||||
found = False
|
||||
if isinstance(cur_struct, (list, tuple)):
|
||||
for cur_ele in cur_struct:
|
||||
found |= _compare(cmp_value, cur_ele)
|
||||
elif isinstance(cur_struct, dict):
|
||||
for _, cur_ele in six.iteritems(cur_struct):
|
||||
found |= _compare(cmp_value, cur_ele)
|
||||
return found
|
||||
else:
|
||||
if isinstance(cmp_value, dict):
|
||||
if cmp_key not in cur_struct:
|
||||
return False
|
||||
return _compare(cmp_value, cur_struct[cmp_key])
|
||||
if isinstance(cmp_value, list):
|
||||
found = False
|
||||
for _, cur_struct_val in six.iteritems(cur_struct):
|
||||
found |= _compare(cmp_value, cur_struct_val)
|
||||
return found
|
||||
else:
|
||||
return _compare(cmp_value, cur_struct[cmp_key])
|
||||
elif isinstance(cur_cmp, (list, tuple)) and isinstance(cur_struct, (list, tuple)):
|
||||
log.debug('Comparing list to list')
|
||||
found = False
|
||||
for cur_cmp_ele in cur_cmp:
|
||||
for cur_struct_ele in cur_struct:
|
||||
found |= _compare(cur_cmp_ele, cur_struct_ele)
|
||||
return found
|
||||
elif isinstance(cur_cmp, dict) and isinstance(cur_struct, (list, tuple)):
|
||||
log.debug('Comparing dict to list (of dicts?)')
|
||||
found = False
|
||||
for cur_struct_ele in cur_struct:
|
||||
found |= _compare(cur_cmp, cur_struct_ele)
|
||||
return found
|
||||
elif isinstance(cur_cmp, bool) and isinstance(cur_struct, bool):
|
||||
log.debug('Comparing booleans: %s ? %s', cur_cmp, cur_struct)
|
||||
return cur_cmp == cur_struct
|
||||
elif isinstance(cur_cmp, (six.string_types, six.text_type)) and \
|
||||
isinstance(cur_struct, (six.string_types, six.text_type)):
|
||||
log.debug('Comparing strings (and regex?): %s ? %s', cur_cmp, cur_struct)
|
||||
# Trying literal match
|
||||
matched = re.match(cur_cmp, cur_struct, re.I)
|
||||
if matched:
|
||||
return True
|
||||
return False
|
||||
elif isinstance(cur_cmp, (six.integer_types, float)) and \
|
||||
isinstance(cur_struct, (six.integer_types, float)):
|
||||
log.debug('Comparing numeric values: %d ? %d', cur_cmp, cur_struct)
|
||||
# numeric compare
|
||||
return cur_cmp == cur_struct
|
||||
elif isinstance(cur_struct, (six.integer_types, float)) and \
|
||||
isinstance(cur_cmp, (six.string_types, six.text_type)):
|
||||
# Comapring the numerical value agains a presumably mathematical value
|
||||
log.debug('Comparing a numeric value (%d) with a string (%s)', cur_struct, cur_cmp)
|
||||
numeric_compare = _numeric_regex.match(cur_cmp)
|
||||
# determine if the value to compare agains is a mathematical operand
|
||||
if numeric_compare:
|
||||
compare_value = numeric_compare.group(2)
|
||||
return getattr(float(cur_struct), _numeric_operand[numeric_compare.group(1)])(float(compare_value))
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def validate(config):
|
||||
'''
|
||||
Validate the beacon configuration.
|
||||
'''
|
||||
# Must be a list of dicts.
|
||||
if not isinstance(config, list):
|
||||
return False, 'Configuration for napalm beacon must be a list.'
|
||||
for mod in config:
|
||||
fun = mod.keys()[0]
|
||||
fun_cfg = mod.values()[0]
|
||||
if not isinstance(fun_cfg, dict):
|
||||
return False, 'The match structure for the {} execution function output must be a dictionary'.format(fun)
|
||||
if fun not in __salt__:
|
||||
return False, 'Execution function {} is not availabe!'.format(fun)
|
||||
return True, 'Valid configuration for the napal beacon!'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
'''
|
||||
Watch napalm function and fire events.
|
||||
'''
|
||||
log.debug('Executing napalm beacon with config:')
|
||||
log.debug(config)
|
||||
ret = []
|
||||
for mod in config:
|
||||
if not mod:
|
||||
continue
|
||||
event = {}
|
||||
fun = mod.keys()[0]
|
||||
fun_cfg = mod.values()[0]
|
||||
args = fun_cfg.pop('_args', [])
|
||||
kwargs = fun_cfg.pop('_kwargs', {})
|
||||
log.debug('Executing {fun} with {args} and {kwargs}'.format(
|
||||
fun=fun,
|
||||
args=args,
|
||||
kwargs=kwargs
|
||||
))
|
||||
fun_ret = __salt__[fun](*args, **kwargs)
|
||||
log.debug('Got the reply from the minion:')
|
||||
log.debug(fun_ret)
|
||||
if not fun_ret.get('result', False):
|
||||
log.error('Error whilst executing {}'.format(fun))
|
||||
log.error(fun_ret)
|
||||
continue
|
||||
fun_ret_out = fun_ret['out']
|
||||
log.debug('Comparing to:')
|
||||
log.debug(fun_cfg)
|
||||
try:
|
||||
fun_cmp_result = _compare(fun_cfg, fun_ret_out)
|
||||
except Exception as err:
|
||||
log.error(err, exc_info=True)
|
||||
# catch any exception and continue
|
||||
# to not jeopardise the execution of the next function in the list
|
||||
continue
|
||||
log.debug('Result of comparison: {res}'.format(res=fun_cmp_result))
|
||||
if fun_cmp_result:
|
||||
log.info('Matched {fun} with {cfg}'.format(
|
||||
fun=fun,
|
||||
cfg=fun_cfg
|
||||
))
|
||||
event['tag'] = '{os}/{fun}'.format(os=__grains__['os'], fun=fun)
|
||||
event['fun'] = fun
|
||||
event['args'] = args
|
||||
event['kwargs'] = kwargs
|
||||
event['data'] = fun_ret
|
||||
event['match'] = fun_cfg
|
||||
log.debug('Queueing event:')
|
||||
log.debug(event)
|
||||
ret.append(event)
|
||||
log.debug('NAPALM beacon generated the events:')
|
||||
log.debug(ret)
|
||||
return ret
|
5
setup.py
5
setup.py
@ -234,6 +234,7 @@ class GenerateSaltSyspaths(Command):
|
||||
spm_formula_path=self.distribution.salt_spm_formula_dir,
|
||||
spm_pillar_path=self.distribution.salt_spm_pillar_dir,
|
||||
spm_reactor_path=self.distribution.salt_spm_reactor_dir,
|
||||
home_dir=self.distribution.salt_home_dir,
|
||||
)
|
||||
)
|
||||
|
||||
@ -724,6 +725,7 @@ PIDFILE_DIR = {pidfile_dir!r}
|
||||
SPM_FORMULA_PATH = {spm_formula_path!r}
|
||||
SPM_PILLAR_PATH = {spm_pillar_path!r}
|
||||
SPM_REACTOR_PATH = {spm_reactor_path!r}
|
||||
HOME_DIR = {home_dir!r}
|
||||
'''
|
||||
|
||||
|
||||
@ -868,6 +870,8 @@ class SaltDistribution(distutils.dist.Distribution):
|
||||
'Salt\'s pre-configured SPM pillar directory'),
|
||||
('salt-spm-reactor-dir=', None,
|
||||
'Salt\'s pre-configured SPM reactor directory'),
|
||||
('salt-home-dir=', None,
|
||||
'Salt\'s pre-configured user home directory'),
|
||||
]
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
@ -892,6 +896,7 @@ class SaltDistribution(distutils.dist.Distribution):
|
||||
self.salt_spm_formula_dir = None
|
||||
self.salt_spm_pillar_dir = None
|
||||
self.salt_spm_reactor_dir = None
|
||||
self.salt_home_dir = None
|
||||
|
||||
self.name = 'salt-ssh' if PACKAGED_FOR_SALT_SSH else 'salt'
|
||||
self.salt_version = __version__ # pylint: disable=undefined-variable
|
||||
|
Loading…
Reference in New Issue
Block a user