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/netntp.py |
""" Network NTP =========== .. versionadded:: 2016.11.0 Manage the configuration of NTP peers and servers on the network devices through the NAPALM proxy. :codeauthor: Mircea Ulinic <ping@mirceaulinic.net> & Jerome Fleury <jf@cloudflare.com> :maturity: new :depends: napalm :platform: unix Dependencies ------------ - Requires netaddr_ to be installed: `pip install netaddr` to check if IP Addresses are correctly specified - Requires dnspython_ to be installed: `pip install dnspython` to resolve the nameserver entities (in case the user does not configure the peers/servers using their IP addresses) - :mod:`NAPALM proxy minion <salt.proxy.napalm>` - :mod:`NTP operational and configuration management module <salt.modules.napalm_ntp>` .. _netaddr: https://pythonhosted.org/netaddr/ .. _dnspython: http://www.dnspython.org/ """ import logging import salt.utils.napalm try: from netaddr import IPAddress from netaddr.core import AddrFormatError HAS_NETADDR = True except ImportError: HAS_NETADDR = False try: import dns.resolver # pylint: disable=no-name-in-module HAS_DNSRESOLVER = True except ImportError: HAS_DNSRESOLVER = False # ---------------------------------------------------------------------------------------------------------------------- # state properties # ---------------------------------------------------------------------------------------------------------------------- __virtualname__ = "netntp" log = logging.getLogger(__name__) # ---------------------------------------------------------------------------------------------------------------------- # 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 _default_ret(name): ret = {"name": name, "changes": {}, "result": False, "comment": ""} return ret def _retrieve_ntp_peers(): """Retrieves configured NTP peers""" return __salt__["ntp.peers"]() def _retrieve_ntp_servers(): """Retrieves configured NTP servers""" return __salt__["ntp.servers"]() def _check(peers): """Checks whether the input is a valid list of peers and transforms domain names into IP Addresses""" if not isinstance(peers, list): return False for peer in peers: if not isinstance(peer, str): return False if ( not HAS_NETADDR ): # if does not have this lib installed, will simply try to load what user specified # if the addresses are not correctly specified, will trow error when loading the actual config return True ip_only_peers = [] for peer in peers: try: ip_only_peers.append(str(IPAddress(peer))) # append the str value except AddrFormatError: # if not a valid IP Address # will try to see if it is a nameserver and resolve it if not HAS_DNSRESOLVER: continue # without the dns resolver cannot populate the list of NTP entities based on their nameserver # so we'll move on dns_reply = [] try: # try to see if it is a valid NS dns_reply = dns.resolver.query(peer) except dns.resolver.NoAnswer: # no a valid DNS entry either return False for dns_ip in dns_reply: ip_only_peers.append(str(dns_ip)) peers = ip_only_peers return True def _clean(lst): return [elem for elem in lst if elem] def _set_ntp_peers(peers): """Calls ntp.set_peers.""" return __salt__["ntp.set_peers"](*peers, commit=False) def _set_ntp_servers(servers): """Calls ntp.set_servers.""" return __salt__["ntp.set_servers"](*servers, commit=False) def _delete_ntp_peers(peers): """Calls ntp.delete_peers.""" return __salt__["ntp.delete_peers"](*peers, commit=False) def _delete_ntp_servers(servers): """Calls ntp.delete_servers.""" return __salt__["ntp.delete_servers"](*servers, commit=False) def _exec_fun(name, *kargs): if name in list(globals().keys()): return globals().get(name)(*kargs) return None def _check_diff_and_configure(fun_name, peers_servers, name="peers"): _ret = _default_ret(fun_name) _options = ["peers", "servers"] if name not in _options: return _ret _retrieve_fun = f"_retrieve_ntp_{name}" ntp_list_output = _exec_fun( _retrieve_fun ) # contains only IP Addresses as dictionary keys if ntp_list_output.get("result", False) is False: _ret["comment"] = "Cannot retrieve NTP {what} from the device: {reason}".format( what=name, reason=ntp_list_output.get("comment") ) return _ret configured_ntp_list = set(ntp_list_output.get("out", {})) desired_ntp_list = set(peers_servers) if configured_ntp_list == desired_ntp_list: _ret.update( { "comment": f"NTP {name} already configured as needed.", "result": True, } ) return _ret list_to_set = list(desired_ntp_list - configured_ntp_list) list_to_delete = list(configured_ntp_list - desired_ntp_list) list_to_set = _clean(list_to_set) list_to_delete = _clean(list_to_delete) changes = {} if list_to_set: changes["added"] = list_to_set if list_to_delete: changes["removed"] = list_to_delete _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_ntp_peers and _delete_ntp_peers as needed -------------------------------------------------------> expected_config_change = False successfully_changed = True comment = "" if list_to_set: _set_fun = f"_set_ntp_{name}" _set = _exec_fun(_set_fun, list_to_set) if _set.get("result"): expected_config_change = True else: # something went wrong... successfully_changed = False comment += "Cannot set NTP {what}: {reason}".format( what=name, reason=_set.get("comment") ) if list_to_delete: _delete_fun = f"_delete_ntp_{name}" _removed = _exec_fun(_delete_fun, list_to_delete) if _removed.get("result"): expected_config_change = True else: # something went wrong... successfully_changed = False comment += "Cannot remove NTP {what}: {reason}".format( what=name, reason=_removed.get("comment") ) _ret.update( { "successfully_changed": successfully_changed, "expected_config_change": expected_config_change, "comment": comment, } ) return _ret # ---------------------------------------------------------------------------------------------------------------------- # callable functions # ---------------------------------------------------------------------------------------------------------------------- def managed(name, peers=None, servers=None): """ Manages the configuration of NTP peers and servers on the device, as specified in the state SLS file. NTP entities not specified in these lists will be removed whilst entities not configured on the device will be set. SLS Example: .. code-block:: yaml netntp_example: netntp.managed: - peers: - 192.168.0.1 - 172.17.17.1 - servers: - 24.124.0.251 - 138.236.128.36 Output example: .. code-block:: python { 'edge01.nrt04': { 'netntp_|-netntp_example_|-netntp_example_|-managed': { 'comment': 'NTP servers already configured as needed.', 'name': 'netntp_example', 'start_time': '12:45:24.056659', 'duration': 2938.857, 'changes': { 'peers': { 'removed': [ '192.168.0.2', '192.168.0.3' ], 'added': [ '192.168.0.1', '172.17.17.1' ] } }, 'result': None } } } """ ret = _default_ret(name) result = ret.get("result", False) comment = ret.get("comment", "") changes = ret.get("changes", {}) if not ( isinstance(peers, list) or isinstance(servers, list) ): # none of the is a list return ret # just exit if isinstance(peers, list) and not _check(peers): # check and clean peers ret["comment"] = ( "NTP peers must be a list of valid IP Addresses or Domain Names" ) return ret if isinstance(servers, list) and not _check(servers): # check and clean servers ret["comment"] = ( "NTP servers must be a list of valid IP Addresses or Domain Names" ) return ret # ----- Retrieve existing NTP peers and determine peers to be added/removed ---------------------------------------> successfully_changed = True expected_config_change = False if isinstance(peers, list): _peers_ret = _check_diff_and_configure(name, peers, name="peers") expected_config_change = _peers_ret.get("expected_config_change", False) successfully_changed = _peers_ret.get("successfully_changed", True) result = result and _peers_ret.get("result", False) comment += "\n" + _peers_ret.get("comment", "") _changed_peers = _peers_ret.get("changes", {}) if _changed_peers: changes["peers"] = _changed_peers if isinstance(servers, list): _servers_ret = _check_diff_and_configure(name, servers, name="servers") expected_config_change = expected_config_change or _servers_ret.get( "expected_config_change", False ) successfully_changed = successfully_changed and _servers_ret.get( "successfully_changed", True ) result = result and _servers_ret.get("result", False) comment += "\n" + _servers_ret.get("comment", "") _changed_servers = _servers_ret.get("changes", {}) if _changed_servers: changes["servers"] = _changed_servers ret.update({"changes": changes}) if not (changes or expected_config_change): ret.update({"result": True, "comment": "Device configured properly."}) return ret if __opts__["test"] is True: ret.update( { "result": None, "comment": ( "This is in testing mode, the device configuration was not changed!" ), } ) return ret # <---- Call _set_ntp_peers and _delete_ntp_peers as needed -------------------------------------------------------- # ----- Try to commit changes -------------------------------------------------------------------------------------> if expected_config_change: # commit only in case there's something to update config_result, config_comment = __salt__["net.config_control"]() result = config_result and successfully_changed comment += config_comment # <---- Try to commit changes -------------------------------------------------------------------------------------- ret.update({"result": result, "comment": comment}) return ret