1# 2# -*- coding: utf-8 -*- 3# Copyright 2019 Red Hat 4# GNU General Public License v3.0+ 5# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6""" 7The junos_lldp_interfaces class 8It is in this file where the current configuration (as dict) 9is compared to the provided configuration (as dict) and the command set 10necessary to bring the current configuration to it's desired end-state is 11created 12""" 13from __future__ import absolute_import, division, print_function 14 15__metaclass__ = type 16 17from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( 18 ConfigBase, 19) 20from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( 21 to_list, 22) 23from ansible_collections.junipernetworks.junos.plugins.module_utils.network.junos.facts.facts import ( 24 Facts, 25) 26from ansible_collections.junipernetworks.junos.plugins.module_utils.network.junos.junos import ( 27 locked_config, 28 load_config, 29 commit_configuration, 30 discard_changes, 31 tostring, 32) 33from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.netconf import ( 34 build_root_xml_node, 35 build_child_xml_node, 36 build_subtree, 37) 38 39 40class Lldp_interfaces(ConfigBase): 41 """ 42 The junos_lldp_interfaces class 43 """ 44 45 gather_subset = ["!all", "!min"] 46 47 gather_network_resources = ["lldp_interfaces"] 48 49 def __init__(self, module): 50 super(Lldp_interfaces, self).__init__(module) 51 52 def get_lldp_interfaces_facts(self, data=None): 53 """ Get the 'facts' (the current configuration) 54 :rtype: A dictionary 55 :returns: The current configuration as a dictionary 56 """ 57 facts, _warnings = Facts(self._module).get_facts( 58 self.gather_subset, self.gather_network_resources, data=data 59 ) 60 lldp_interfaces_facts = facts["ansible_network_resources"].get( 61 "lldp_interfaces" 62 ) 63 if not lldp_interfaces_facts: 64 return [] 65 return lldp_interfaces_facts 66 67 def execute_module(self): 68 """ Execute the module 69 :rtype: A dictionary 70 :returns: The result from module execution 71 """ 72 result = {"changed": False} 73 state = self._module.params["state"] 74 warnings = list() 75 76 if self.state in self.ACTION_STATES: 77 existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() 78 else: 79 existing_lldp_interfaces_facts = [] 80 if state == "gathered": 81 existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() 82 result["gathered"] = existing_lldp_interfaces_facts 83 elif self.state == "parsed": 84 running_config = self._module.params["running_config"] 85 if not running_config: 86 self._module.fail_json( 87 msg="value of running_config parameter must not be empty for state parsed" 88 ) 89 result["parsed"] = self.get_lldp_interfaces_facts( 90 data=running_config 91 ) 92 elif self.state == "rendered": 93 config_xmls = self.set_config(existing_lldp_interfaces_facts) 94 if config_xmls: 95 result["rendered"] = config_xmls[0] 96 else: 97 result["rendered"] = "" 98 99 else: 100 config_xmls = self.set_config(existing_lldp_interfaces_facts) 101 with locked_config(self._module): 102 for config_xml in to_list(config_xmls): 103 diff = load_config(self._module, config_xml, []) 104 105 commit = not self._module.check_mode 106 if diff: 107 if commit: 108 commit_configuration(self._module) 109 else: 110 discard_changes(self._module) 111 result["changed"] = True 112 113 if self._module._diff: 114 result["diff"] = {"prepared": diff} 115 116 result["commands"] = config_xmls 117 118 changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts() 119 120 result["before"] = existing_lldp_interfaces_facts 121 if result["changed"]: 122 result["after"] = changed_lldp_interfaces_facts 123 124 result["warnings"] = warnings 125 126 return result 127 128 def set_config(self, existing_lldp_interfaces_facts): 129 """ Collect the configuration from the args passed to the module, 130 collect the current configuration (as a dict from facts) 131 :rtype: A list 132 :returns: the commands necessary to migrate the current configuration 133 to the desired configuration 134 """ 135 want = self._module.params["config"] 136 have = existing_lldp_interfaces_facts 137 resp = self.set_state(want, have) 138 return to_list(resp) 139 140 def set_state(self, want, have): 141 """ Select the appropriate function based on the state provided 142 :param want: the desired configuration as a dictionary 143 :param have: the current configuration as a dictionary 144 :rtype: A list 145 :returns: the commands necessary to migrate the current configuration 146 to the desired configuration 147 """ 148 root = build_root_xml_node("protocols") 149 lldp_intf_ele = build_subtree(root, "lldp") 150 151 state = self._module.params["state"] 152 if ( 153 state in ("merged", "replaced", "overridden", "rendered") 154 and not want 155 ): 156 self._module.fail_json( 157 msg="value of config parameter must not be empty for state {0}".format( 158 state 159 ) 160 ) 161 if state == "overridden": 162 config_xmls = self._state_overridden(want, have) 163 elif state == "deleted": 164 config_xmls = self._state_deleted(want, have) 165 elif state in ("merged", "rendered"): 166 config_xmls = self._state_merged(want, have) 167 elif state == "replaced": 168 config_xmls = self._state_replaced(want, have) 169 170 for xml in config_xmls: 171 lldp_intf_ele.append(xml) 172 173 return tostring(root) 174 175 def _state_replaced(self, want, have): 176 """ The xml configuration generator when state is replaced 177 :rtype: A list 178 :returns: the xml configuration necessary to migrate the current configuration 179 to the desired configuration 180 """ 181 lldp_intf_xml = [] 182 lldp_intf_xml.extend(self._state_deleted(want, have)) 183 lldp_intf_xml.extend(self._state_merged(want, have)) 184 185 return lldp_intf_xml 186 187 def _state_overridden(self, want, have): 188 """ The xml configuration generator when state is overridden 189 :rtype: A list 190 :returns: the xml configuration necessary to migrate the current configuration 191 to the desired configuration 192 """ 193 lldp_intf_xmls_obj = [] 194 195 # replace interface config with data in want 196 lldp_intf_xmls_obj.extend(self._state_replaced(want, have)) 197 198 # delete interface config if interface in have not present in want 199 delete_obj = [] 200 for have_obj in have: 201 for want_obj in want: 202 if have_obj["name"] == want_obj["name"]: 203 break 204 else: 205 delete_obj.append(have_obj) 206 207 if len(delete_obj): 208 lldp_intf_xmls_obj.extend(self._state_deleted(delete_obj, have)) 209 210 return lldp_intf_xmls_obj 211 212 def _state_merged(self, want, have): 213 """ The xml configuration generator when state is merged 214 :rtype: A list 215 :returns: the xml configuration necessary to merge the provided into 216 the current configuration 217 """ 218 lldp_intf_xml = [] 219 for config in want: 220 lldp_intf_root = build_root_xml_node("interface") 221 222 if config.get("name"): 223 build_child_xml_node(lldp_intf_root, "name", config["name"]) 224 225 if config.get("enabled") is not None: 226 if config["enabled"] is False: 227 build_child_xml_node(lldp_intf_root, "disable") 228 else: 229 build_child_xml_node( 230 lldp_intf_root, "disable", None, {"delete": "delete"} 231 ) 232 else: 233 build_child_xml_node( 234 lldp_intf_root, "disable", None, {"delete": "delete"} 235 ) 236 lldp_intf_xml.append(lldp_intf_root) 237 return lldp_intf_xml 238 239 def _state_deleted(self, want, have): 240 """ The xml configuration generator when state is deleted 241 :rtype: A list 242 :returns: the xml configuration necessary to remove the current configuration 243 of the provided objects 244 """ 245 lldp_intf_xml = [] 246 intf_obj = want 247 248 if not intf_obj: 249 # delete lldp interfaces attribute from all the existing interface 250 intf_obj = have 251 252 for config in intf_obj: 253 lldp_intf_root = build_root_xml_node("interface") 254 lldp_intf_root.attrib.update({"delete": "delete"}) 255 build_child_xml_node(lldp_intf_root, "name", config["name"]) 256 257 lldp_intf_xml.append(lldp_intf_root) 258 259 return lldp_intf_xml 260