%PDF- %PDF-
Direktori : /lib/python2.7/site-packages/salt/beacons/ |
Current File : //lib/python2.7/site-packages/salt/beacons/napalm_beacon.py |
# -*- coding: utf-8 -*- ''' Watch NAPALM functions and fire events on specific triggers =========================================================== .. versionadded:: 2018.3.0 .. 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 { "_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, unicode_literals # 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