PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/beacons/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/beacons/napalm_beacon.py |
""" 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. """ import logging import re import salt.utils.beacons 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. """ if salt.utils.napalm.virtual(__opts__, __virtualname__, __file__): return __virtualname__ else: err_msg = "NAPALM is not installed." log.error("Unable to load %s beacon: %s", __virtualname__, err_msg) return False, err_msg 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 cur_cmp.items(): if cmp_key == "*": # matches any key from the source dictionary if isinstance(cmp_value, dict): found = False for _, cur_struct_val in cur_struct.items(): 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 cur_struct.items(): 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 cur_struct.items(): 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, ((str,), str)) and isinstance(cur_struct, ((str,), str)): 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, ((int,), float)) and isinstance( cur_struct, ((int,), float) ): log.debug("Comparing numeric values: %d ? %d", cur_cmp, cur_struct) # numeric compare return cur_cmp == cur_struct elif isinstance(cur_struct, ((int,), float)) and isinstance(cur_cmp, ((str,), str)): # Comparing the numerical value against 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 against 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, fun_cfg = next(iter(mod.items())) 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, f"Execution function {fun} is not availabe!" return True, "Valid configuration for the napal beacon!" def beacon(config): """ Watch napalm function and fire events. """ whitelist = [] config = salt.utils.beacons.remove_hidden_options(config, whitelist) log.debug("Executing napalm beacon with config:") log.debug(config) ret = [] for mod in config: if not mod: continue event = {} fun, fun_cfg = next(iter(mod.items())) args = fun_cfg.pop("_args", []) kwargs = fun_cfg.pop("_kwargs", {}) log.debug("Executing %s with %s and %s", fun, args, 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 %s", 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: # pylint: disable=broad-except 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: %s", fun_cmp_result) if fun_cmp_result: log.info("Matched %s with %s", fun, 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