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/states/ |
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/states/netsnmp.py |
""" Network SNMP ============ Manage the SNMP configuration on network devices. :codeauthor: Mircea Ulinic <ping@mirceaulinic.net> :maturity: new :depends: napalm :platform: unix Dependencies ------------ - :mod:`napalm snmp management module (salt.modules.napalm_snmp) <salt.modules.napalm_snmp>` .. versionadded:: 2016.11.0 """ import logging import salt.utils.json import salt.utils.napalm log = logging.getLogger(__name__) # ---------------------------------------------------------------------------------------------------------------------- # state properties # ---------------------------------------------------------------------------------------------------------------------- __virtualname__ = "netsnmp" _COMMUNITY_MODE_MAP = { "read-only": "ro", "readonly": "ro", "read-write": "rw", "write": "rw", } # ---------------------------------------------------------------------------------------------------------------------- # global variables # ---------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------- # property functions # ---------------------------------------------------------------------------------------------------------------------- def __virtual__(): """ NAPALM library must be installed for this module to work and run in a (proxy) minion. """ return salt.utils.napalm.virtual(__opts__, __virtualname__, __file__) # ---------------------------------------------------------------------------------------------------------------------- # helper functions -- will not be exported # ---------------------------------------------------------------------------------------------------------------------- def _ordered_dict_to_dict(config): """ Forced the datatype to dict, in case OrderedDict is used. """ return salt.utils.json.loads(salt.utils.json.dumps(config)) def _expand_config(config, defaults): """ Completed the values of the expected config for the edge cases with the default values. """ defaults.update(config) return defaults def _valid_dict(dic): """ Valid dictionary? """ return isinstance(dic, dict) and len(dic) > 0 def _valid_str(value): """ Valid str? """ return isinstance(value, str) and len(value) > 0 def _community_defaults(): """ Returns the default values of a community. """ return {"mode": "ro"} def _clear_community_details(community_details): """ Clears community details. """ for key in ["acl", "mode"]: _str_elem(community_details, key) _mode = community_details.get["mode"] = community_details.get("mode").lower() if _mode in _COMMUNITY_MODE_MAP: community_details["mode"] = _COMMUNITY_MODE_MAP.get(_mode) if community_details["mode"] not in ["ro", "rw"]: community_details["mode"] = "ro" # default is read-only return community_details def _str_elem(config, key): """ Re-adds the value of a specific key in the dict, only in case of valid str value. """ _value = config.pop(key, "") if _valid_str(_value): config[key] = _value def _check_config(config): """ Checks the desired config and clears interesting details. """ if not _valid_dict(config): return True, "" _community = config.get("community") _community_tmp = {} if not _community: return False, "Must specify at least a community." if _valid_str(_community): _community_tmp[_community] = _community_defaults() elif isinstance(_community, list): # if the user specifies the communities as list for _comm in _community: if _valid_str(_comm): # list of values _community_tmp[_comm] = _community_defaults() # default mode is read-only if _valid_dict(_comm): # list of dicts for _comm_name, _comm_details in _comm.items(): if _valid_str(_comm_name): _community_tmp[_comm_name] = _clear_community_details( _comm_details ) elif _valid_dict(_community): # directly as dict of communities # recommended way... for _comm_name, _comm_details in _community.items(): if _valid_str(_comm_name): _community_tmp[_comm_name] = _clear_community_details(_comm_details) else: return False, "Please specify a community or a list of communities." if not _valid_dict(_community_tmp): return False, "Please specify at least a valid community!" config["community"] = _community_tmp for key in ["location", "contact", "chassis_id"]: # not mandatory, but should be here only if valid _str_elem(config, key) return True, "" def _retrieve_device_config(): """ Retrieves the SNMP config from the device. """ return __salt__["snmp.config"]() def _create_diff_action(diff, diff_key, key, value): """ DRY to build diff parts (added, removed, updated). """ if diff_key not in diff: diff[diff_key] = {} diff[diff_key][key] = value def _create_diff(diff, fun, key, prev, curr): """ Builds the diff dictionary. """ if not fun(prev): _create_diff_action(diff, "added", key, curr) elif fun(prev) and not fun(curr): _create_diff_action(diff, "removed", key, prev) elif not fun(curr): _create_diff_action(diff, "updated", key, curr) def _compute_diff(existing, expected): """ Computes the differences between the existing and the expected SNMP config. """ diff = {} for key in ["location", "contact", "chassis_id"]: if existing.get(key) != expected.get(key): _create_diff(diff, _valid_str, key, existing.get(key), expected.get(key)) for key in ["community"]: # for the moment only onen if existing.get(key) != expected.get(key): _create_diff(diff, _valid_dict, key, existing.get(key), expected.get(key)) return diff def _configure(changes): """ Calls the configuration template to apply the configuration changes on the device. """ cfgred = True reasons = [] fun = "update_config" for key in ["added", "updated", "removed"]: _updated_changes = changes.get(key, {}) if not _updated_changes: continue _location = _updated_changes.get("location", "") _contact = _updated_changes.get("contact", "") _community = _updated_changes.get("community", {}) _chassis_id = _updated_changes.get("chassis_id", "") if key == "removed": fun = "remove_config" _ret = __salt__[f"snmp.{fun}"]( location=_location, contact=_contact, community=_community, chassis_id=_chassis_id, commit=False, ) cfgred = cfgred and _ret.get("result") if not _ret.get("result") and _ret.get("comment"): reasons.append(_ret.get("comment")) return {"result": cfgred, "comment": "\n".join(reasons) if reasons else ""} # ---------------------------------------------------------------------------------------------------------------------- # callable functions # ---------------------------------------------------------------------------------------------------------------------- def managed(name, config=None, defaults=None): """ Configures the SNMP on the device as specified in the SLS file. SLS Example: .. code-block:: yaml snmp_example: netsnmp.managed: - config: location: Honolulu, HI, US - defaults: contact: noc@cloudflare.com Output example (for the SLS above, e.g. called snmp.sls under /router/): .. code-block:: bash $ sudo salt edge01.hnl01 state.sls router.snmp test=True edge01.hnl01: ---------- ID: snmp_example Function: snmp.managed Result: None Comment: Testing mode: configuration was not changed! Started: 13:29:06.872363 Duration: 920.466 ms Changes: ---------- added: ---------- chassis_id: None contact: noc@cloudflare.com location: Honolulu, HI, US Summary for edge01.hnl01 ------------ Succeeded: 1 (unchanged=1, changed=1) Failed: 0 ------------ Total states run: 1 Total run time: 920.466 ms """ result = False comment = "" changes = {} ret = {"name": name, "changes": changes, "result": result, "comment": comment} # make sure we're working only with dict config = _ordered_dict_to_dict(config) defaults = _ordered_dict_to_dict(defaults) expected_config = _expand_config(config, defaults) if not isinstance(expected_config, dict): ret["comment"] = "User provided an empty SNMP config!" return ret valid, message = _check_config(expected_config) if not valid: # check and clean ret["comment"] = "Please provide a valid configuration: {error}".format( error=message ) return ret # ----- Retrieve existing users configuration and determine differences -------------------------------------------> _device_config = _retrieve_device_config() if not _device_config.get("result"): ret["comment"] = "Cannot retrieve SNMP config from the device: {reason}".format( reason=_device_config.get("comment") ) return ret device_config = _device_config.get("out", {}) if device_config == expected_config: ret.update({"comment": "SNMP already configured as needed.", "result": True}) return ret diff = _compute_diff(device_config, expected_config) changes.update(diff) ret.update({"changes": changes}) if __opts__["test"] is True: ret.update( {"result": None, "comment": "Testing mode: configuration was not changed!"} ) return ret # <---- Retrieve existing NTP peers and determine peers to be added/removed ---------------------------------------> # ----- Call _set_users and _delete_users as needed -------------------------------------------------------> expected_config_change = False result = True if diff: _configured = _configure(diff) if _configured.get("result"): expected_config_change = True else: # something went wrong... result = False comment = ( "Cannot push new SNMP config: \n{reason}".format( reason=_configured.get("comment") ) + comment ) if expected_config_change: result, comment = __salt__["net.config_control"]() # <---- Call _set_users and _delete_users as needed -------------------------------------------------------- ret.update({"result": result, "comment": comment}) return ret