From b046e2a08b4ee3ae6abd9fbdad3e0c0a4e0d521f Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Fri, 2 Jun 2017 16:00:39 +0000 Subject: [PATCH 1/9] New beacon: NAPALM --- salt/beacons/napalm_beacon.py | 158 ++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 salt/beacons/napalm_beacon.py diff --git a/salt/beacons/napalm_beacon.py b/salt/beacons/napalm_beacon.py new file mode 100644 index 0000000000..789128496c --- /dev/null +++ b/salt/beacons/napalm_beacon.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +''' +Watch napalm function and fire events. + +:depends: - napalm_base Python module >= 0.9.5 + +:note: The ``napalm`` beacon only works on (proxy) minions. +''' +# Import Python libs +from __future__ import absolute_import +import re + +# Import salt libs +from salt.ext import six + +# Import third party libs +try: + import napalm_base + HAS_NAPALM_BASE = True +except ImportError: + HAS_NAPALM_BASE = False + +__virtualname__ = 'napalm' + +import logging +log = logging.getLogger(__name__) + + +def __virtual__(): + if HAS_NAPALM_BASE: + return __virtualname__ + return False + + +def _compare(cur_cmp, cur_struct): + ''' + Compares two obejcts and return a boolean value + when there's a match. + ''' + if isinstance(cur_cmp, dict) and isinstance(cur_struct, 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)): + 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, bool) and isinstance(cur_struct, bool): + 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)): + matched = re.match(cur_cmp, cur_struct, re.I) + if matched: + return True + # we can enhance this to allow mathematical operations + return False + return False + + +def beacon(config): + ''' + Watch napalm function and fire events. + + Example Config + + .. code-block:: yaml + + beacons: + napalm: + - net.interfaces: + '*': + is_up: False + - bgp.neighbors: + _args: + - 172.17.17.1 + global: + '*': + - up: False + ''' + 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 {fun}'.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'] = '{fun}'.format(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 From 8002ee81126ec2dbdf47dfeffc296886d04cd93e Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Mon, 4 Sep 2017 16:41:35 +0000 Subject: [PATCH 2/9] Mathematical comparison and better documentation --- salt/beacons/napalm_beacon.py | 134 +++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 35 deletions(-) diff --git a/salt/beacons/napalm_beacon.py b/salt/beacons/napalm_beacon.py index 789128496c..8e89561b4b 100644 --- a/salt/beacons/napalm_beacon.py +++ b/salt/beacons/napalm_beacon.py @@ -1,35 +1,90 @@ # -*- coding: utf-8 -*- ''' -Watch napalm function and fire events. +NAPALM functions +================ -:depends: - napalm_base Python module >= 0.9.5 +Watch napalm functions and fire events on specific triggers. -:note: The ``napalm`` beacon only works on (proxy) minions. +.. note:: + The ``napalm`` beacon only work only when running under + a regular or a proxy minion. + +The configuration accepts a list of Salt functions to be +invoked, and the corresponding output hierarchy that should +be matched against. When we want to match on any element +at a certain level, we can have ``*`` to match anything. + +To invoke a certain function with certain arguments, +they can be specified using the ``_args`` key, or +``_kwargs`` to configure more specific key-value arguments. + +The right operand can also accept mathematical comparisons +when applicable (i.e., ``<``, ``<=``, ``!=``, ``>``, ``>=`` etc.). + +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 + - bgp.neighbors: + # fire events only when the 172.17.17.1 BGP neighbor is down + _args: + - 172.17.17.1 + global: + '*': + 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' ''' -# Import Python libs from __future__ import absolute_import + +# Import Python std lib import re +import logging -# Import salt libs +# Import Salt modules from salt.ext import six +import salt.utils.napalm -# Import third party libs -try: - import napalm_base - HAS_NAPALM_BASE = True -except ImportError: - HAS_NAPALM_BASE = False +log = logging.getLogger(__name__) +_numeric_regex = re.compile('^(<|>|<=|>=|==|!=)\s*(\d+(\.\d+){0,1})$') +_numeric_operand = { + '<': '__lt__', + '>': '__gt__', + '>=': '__ge__', + '<=': '__le__', + '==': '__eq__', + '!=': '__ne__', +} __virtualname__ = 'napalm' -import logging -log = logging.getLogger(__name__) - def __virtual__(): - if HAS_NAPALM_BASE: - return __virtualname__ - return False + ''' + This beacon can only work when running under a regular or a proxy minion. + ''' + return salt.utils.napalm.virtual(__opts__, __virtualname__, __file__) def _compare(cur_cmp, cur_struct): @@ -38,6 +93,7 @@ def _compare(cur_cmp, cur_struct): 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 @@ -68,19 +124,43 @@ def _compare(cur_cmp, cur_struct): 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') 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?)') + # Trying literal match matched = re.match(cur_cmp, cur_struct, re.I) if matched: return True - # we can enhance this to allow mathematical operations + return False + elif isinstance(cur_cmp, (six.integer_types, float)) and \ + isinstance(cur_struct, (six.integer_types, float)): + log.debug('Comparing numeric values') + # 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 @@ -88,22 +168,6 @@ def _compare(cur_cmp, cur_struct): def beacon(config): ''' Watch napalm function and fire events. - - Example Config - - .. code-block:: yaml - - beacons: - napalm: - - net.interfaces: - '*': - is_up: False - - bgp.neighbors: - _args: - - 172.17.17.1 - global: - '*': - - up: False ''' log.debug('Executing napalm beacon with config:') log.debug(config) @@ -144,7 +208,7 @@ def beacon(config): fun=fun, cfg=fun_cfg )) - event['tag'] = '{fun}'.format(fun=fun) + event['tag'] = '{os}/{fun}'.format(os=__grains__['os'], fun=fun) event['fun'] = fun event['args'] = args event['kwargs'] = kwargs From bfc374abd1f70484d1f2036ef1bbeb37c3571cf1 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Mon, 4 Sep 2017 16:43:29 +0000 Subject: [PATCH 3/9] Index napalm beacon to autodoc --- doc/ref/beacons/all/index.rst | 1 + doc/ref/beacons/all/salt.beacons.napalm_beacon.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 doc/ref/beacons/all/salt.beacons.napalm_beacon.rst diff --git a/doc/ref/beacons/all/index.rst b/doc/ref/beacons/all/index.rst index c0970f4f6c..7fccfc5b15 100644 --- a/doc/ref/beacons/all/index.rst +++ b/doc/ref/beacons/all/index.rst @@ -22,6 +22,7 @@ beacon modules load log memusage + napalm_beacon network_info network_settings pkg diff --git a/doc/ref/beacons/all/salt.beacons.napalm_beacon.rst b/doc/ref/beacons/all/salt.beacons.napalm_beacon.rst new file mode 100644 index 0000000000..ff5bbc4b01 --- /dev/null +++ b/doc/ref/beacons/all/salt.beacons.napalm_beacon.rst @@ -0,0 +1,6 @@ +========================== +salt.beacons.napalm_beacon +========================== + +.. automodule:: salt.beacons.napalm_beacon + :members: From 649cf31da40275cc0575c7deaba3d194d5152633 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Mon, 4 Sep 2017 16:45:23 +0000 Subject: [PATCH 4/9] Doc version added --- salt/beacons/napalm_beacon.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/salt/beacons/napalm_beacon.py b/salt/beacons/napalm_beacon.py index 8e89561b4b..d0631fc911 100644 --- a/salt/beacons/napalm_beacon.py +++ b/salt/beacons/napalm_beacon.py @@ -5,6 +5,8 @@ NAPALM functions Watch napalm functions and fire events on specific triggers. +.. versionadded:: Oxygen + .. note:: The ``napalm`` beacon only work only when running under a regular or a proxy minion. From a0f9903788c2dfc8f3eee04e491478e5e959249a Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 5 Sep 2017 10:04:44 +0000 Subject: [PATCH 5/9] Lint and better documentation --- salt/beacons/napalm_beacon.py | 154 +++++++++++++++++++++++++++++----- 1 file changed, 133 insertions(+), 21 deletions(-) diff --git a/salt/beacons/napalm_beacon.py b/salt/beacons/napalm_beacon.py index d0631fc911..827a80ba39 100644 --- a/salt/beacons/napalm_beacon.py +++ b/salt/beacons/napalm_beacon.py @@ -3,25 +3,104 @@ NAPALM functions ================ -Watch napalm functions and fire events on specific triggers. - .. versionadded:: Oxygen +Watch NAPALM functions and fire events on specific triggers. + .. note:: - The ``napalm`` beacon only work only when running under - a regular or a proxy minion. + + 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 `. + + _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. When we want to match on any element -at a certain level, we can have ``*`` to match anything. +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. -To invoke a certain function with certain arguments, -they can be specified using the ``_args`` key, or -``_kwargs`` to configure more specific key-value arguments. +The match structure follows the output hierarchy of the NAPALM +functions, under the ``out`` key. -The right operand can also accept mathematical comparisons -when applicable (i.e., ``<``, ``<=``, ``!=``, ``>``, ``>=`` etc.). +For example, the following is normal structure returned by the +:mod:`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 ` +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: @@ -37,13 +116,6 @@ Configuration Example: # fire events only when the xe-0/0/0 interface is down 'xe-0/0/0': is_up: false - - bgp.neighbors: - # fire events only when the 172.17.17.1 BGP neighbor is down - _args: - - 172.17.17.1 - global: - '*': - up: false - ntp.stats: # fire when there's any NTP peer unsynchornized synchronized: false @@ -57,6 +129,44 @@ Configuration Example: # 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 @@ -69,7 +179,8 @@ from salt.ext import six import salt.utils.napalm log = logging.getLogger(__name__) -_numeric_regex = re.compile('^(<|>|<=|>=|==|!=)\s*(\d+(\.\d+){0,1})$') +_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__', @@ -77,7 +188,8 @@ _numeric_operand = { '<=': '__le__', '==': '__eq__', '!=': '__ne__', -} +} # mathematical operand - private method map + __virtualname__ = 'napalm' @@ -191,7 +303,7 @@ def beacon(config): log.debug('Got the reply from the minion:') log.debug(fun_ret) if not fun_ret.get('result', False): - log.error('Error whilst executing {fun}'.format(fun)) + log.error('Error whilst executing {}'.format(fun)) log.error(fun_ret) continue fun_ret_out = fun_ret['out'] From c70df4adcd4974362163102f1c675defb03b3a36 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Thu, 14 Sep 2017 16:18:45 -0700 Subject: [PATCH 6/9] Following the change in #42103 if Salt is installed via setup.py then the generated _syspaths.py does not contain the HOME_DIR which results in a failure. --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index effdc2f230..53892807c3 100755 --- a/setup.py +++ b/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} ''' @@ -892,6 +894,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 From fadcc61618fa1b579ac76bd8f41cb34c442f2ac7 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Thu, 14 Sep 2017 18:34:32 -0700 Subject: [PATCH 7/9] Adding home_dir to SaltDistribution.global_options. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 53892807c3..bcdd058d69 100755 --- a/setup.py +++ b/setup.py @@ -868,8 +868,8 @@ class SaltDistribution(distutils.dist.Distribution): 'Salt\'s pre-configured SPM formulas directory'), ('salt-spm-pillar-dir=', None, '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): From 2cdd775ca8bcc00d7b8e1d9bcaac104e1457c131 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Fri, 15 Sep 2017 08:40:38 -0700 Subject: [PATCH 8/9] Adding back in the salt-spm-reactor-dir --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index bcdd058d69..aacebb95a1 100755 --- a/setup.py +++ b/setup.py @@ -868,6 +868,8 @@ class SaltDistribution(distutils.dist.Distribution): 'Salt\'s pre-configured SPM formulas directory'), ('salt-spm-pillar-dir=', None, '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'), ] From 74d1a56524c01c59ced12a5fe8c70c50b8cb23de Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Mon, 18 Sep 2017 10:28:42 +0000 Subject: [PATCH 9/9] Add validate function Also, correct some docstings and improve logged messages --- salt/beacons/napalm_beacon.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/salt/beacons/napalm_beacon.py b/salt/beacons/napalm_beacon.py index 827a80ba39..6650215471 100644 --- a/salt/beacons/napalm_beacon.py +++ b/salt/beacons/napalm_beacon.py @@ -196,14 +196,14 @@ __virtualname__ = 'napalm' def __virtual__(): ''' - This beacon can only work when running under a regular or a proxy minion. + 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 obejcts and return a boolean value + Compares two objects and return a boolean value when there's a match. ''' if isinstance(cur_cmp, dict) and isinstance(cur_struct, dict): @@ -251,11 +251,11 @@ def _compare(cur_cmp, 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') + 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?)') + 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: @@ -263,7 +263,7 @@ def _compare(cur_cmp, cur_struct): return False elif isinstance(cur_cmp, (six.integer_types, float)) and \ isinstance(cur_struct, (six.integer_types, float)): - log.debug('Comparing numeric values') + 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 \ @@ -279,6 +279,23 @@ def _compare(cur_cmp, cur_struct): 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.