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 ios_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 its desired end-state is 11created 12""" 13 14from __future__ import absolute_import, division, print_function 15 16__metaclass__ = type 17 18 19from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( 20 ConfigBase, 21) 22from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( 23 to_list, 24) 25from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import ( 26 Facts, 27) 28from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import ( 29 dict_to_set, 30 normalize_interface, 31) 32from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import ( 33 remove_command_from_config_list, 34 add_command_to_config_list, 35) 36from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import ( 37 filter_dict_having_none_value, 38 remove_duplicate_interface, 39) 40 41 42class Lldp_Interfaces(ConfigBase): 43 """ 44 The ios_lldp_interfaces class 45 """ 46 47 gather_subset = ["!all", "!min"] 48 49 gather_network_resources = ["lldp_interfaces"] 50 51 def __init__(self, module): 52 super(Lldp_Interfaces, self).__init__(module) 53 54 def get_lldp_interfaces_facts(self, data=None): 55 """ Get the 'facts' (the current configuration) 56 57 :rtype: A dictionary 58 :returns: The current configuration as a dictionary 59 """ 60 facts, _warnings = Facts(self._module).get_facts( 61 self.gather_subset, self.gather_network_resources, data=data 62 ) 63 lldp_interfaces_facts = facts["ansible_network_resources"].get( 64 "lldp_interfaces" 65 ) 66 if not lldp_interfaces_facts: 67 return [] 68 return lldp_interfaces_facts 69 70 def execute_module(self): 71 """ Execute the module 72 73 :rtype: A dictionary 74 :returns: The result from module execution 75 """ 76 result = {"changed": False} 77 commands = list() 78 warnings = list() 79 80 if self.state in self.ACTION_STATES: 81 existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() 82 else: 83 existing_lldp_interfaces_facts = [] 84 if self.state in self.ACTION_STATES or self.state == "rendered": 85 commands.extend(self.set_config(existing_lldp_interfaces_facts)) 86 if commands and self.state in self.ACTION_STATES: 87 if not self._module.check_mode: 88 self._connection.edit_config(commands) 89 result["changed"] = True 90 if self.state in self.ACTION_STATES: 91 result["commands"] = commands 92 93 if self.state in self.ACTION_STATES or self.state == "gathered": 94 changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts() 95 elif self.state == "rendered": 96 result["rendered"] = commands 97 elif self.state == "parsed": 98 running_config = self._module.params["running_config"] 99 if not running_config: 100 self._module.fail_json( 101 msg="value of running_config parameter must not be empty for state parsed" 102 ) 103 result["parsed"] = self.get_lldp_interfaces_facts( 104 data=running_config 105 ) 106 else: 107 changed_lldp_interfaces_facts = [] 108 109 if self.state in self.ACTION_STATES: 110 result["before"] = existing_lldp_interfaces_facts 111 if result["changed"]: 112 result["after"] = changed_lldp_interfaces_facts 113 elif self.state == "gathered": 114 result["gathered"] = changed_lldp_interfaces_facts 115 116 result["warnings"] = warnings 117 118 return result 119 120 def set_config(self, existing_lldp_interfaces_facts): 121 """ Collect the configuration from the args passed to the module, 122 collect the current configuration (as a dict from facts) 123 124 :rtype: A list 125 :returns: the commands necessary to migrate the current configuration 126 to the desired configuration 127 """ 128 config = self._module.params.get("config") 129 want = [] 130 if config: 131 for each in config: 132 each.update({"name": normalize_interface(each["name"])}) 133 want.append(each) 134 have = existing_lldp_interfaces_facts 135 resp = self.set_state(want, have) 136 137 return to_list(resp) 138 139 def set_state(self, want, have): 140 """ Select the appropriate function based on the state provided 141 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 149 if ( 150 self.state in ("overridden", "merged", "replaced", "rendered") 151 and not want 152 ): 153 self._module.fail_json( 154 msg="value of config parameter must not be empty for state {0}".format( 155 self.state 156 ) 157 ) 158 159 if self.state == "overridden": 160 commands = self._state_overridden(want, have) 161 elif self.state == "deleted": 162 commands = self._state_deleted(want, have) 163 elif self.state in ("merged", "rendered"): 164 commands = self._state_merged(want, have) 165 elif self.state == "replaced": 166 commands = self._state_replaced(want, have) 167 168 return commands 169 170 def _state_replaced(self, want, have): 171 """ The command generator when state is replaced 172 173 :rtype: A list 174 :returns: the commands necessary to migrate the current configuration 175 to the desired configuration 176 """ 177 commands = [] 178 179 for interface in want: 180 for each in have: 181 if each["name"] == interface["name"]: 182 break 183 else: 184 continue 185 have_dict = filter_dict_having_none_value(interface, each) 186 commands.extend(self._clear_config(dict(), have_dict)) 187 commands.extend(self._set_config(interface, each)) 188 # Remove the duplicate interface call 189 commands = remove_duplicate_interface(commands) 190 191 return commands 192 193 def _state_overridden(self, want, have): 194 """ The command generator when state is overridden 195 196 :rtype: A list 197 :returns: the commands necessary to migrate the current configuration 198 to the desired configuration 199 """ 200 commands = [] 201 202 for each in have: 203 for interface in want: 204 if each["name"] == interface["name"]: 205 break 206 else: 207 # We didn't find a matching desired state, which means we can 208 # pretend we received an empty desired state. 209 interface = dict(name=each["name"]) 210 commands.extend(self._clear_config(interface, each)) 211 continue 212 have_dict = filter_dict_having_none_value(interface, each) 213 commands.extend(self._clear_config(dict(), have_dict)) 214 commands.extend(self._set_config(interface, each)) 215 # Remove the duplicate interface call 216 commands = remove_duplicate_interface(commands) 217 218 return commands 219 220 def _state_merged(self, want, have): 221 """ The command generator when state is merged 222 223 :rtype: A list 224 :returns: the commands necessary to merge the provided into 225 the current configuration 226 """ 227 commands = [] 228 229 for interface in want: 230 for each in have: 231 if interface["name"] == each["name"]: 232 break 233 else: 234 if self.state == "rendered": 235 commands.extend(self._set_config(interface, dict())) 236 continue 237 commands.extend(self._set_config(interface, each)) 238 239 return commands 240 241 def _state_deleted(self, want, have): 242 """ The command generator when state is deleted 243 244 :rtype: A list 245 :returns: the commands necessary to remove the current configuration 246 of the provided objects 247 """ 248 commands = [] 249 250 if want: 251 for interface in want: 252 for each in have: 253 if each["name"] == interface["name"]: 254 break 255 else: 256 continue 257 interface = dict(name=interface["name"]) 258 commands.extend(self._clear_config(interface, each)) 259 else: 260 for each in have: 261 commands.extend(self._clear_config(dict(), each)) 262 263 return commands 264 265 def _set_config(self, want, have): 266 # Set the interface config based on the want and have config 267 commands = [] 268 269 interface = "interface " + want["name"] 270 # Get the diff b/w want and have 271 want_dict = dict_to_set(want) 272 have_dict = dict_to_set(have) 273 diff = want_dict - have_dict 274 275 if diff: 276 diff = dict(diff) 277 receive = diff.get("receive") 278 transmit = diff.get("transmit") 279 med_tlv_select = diff.get("med_tlv_select") 280 tlv_select = diff.get("tlv_select") 281 if receive: 282 cmd = "lldp receive" 283 add_command_to_config_list(interface, cmd, commands) 284 elif receive is False: 285 cmd = "no lldp receive" 286 add_command_to_config_list(interface, cmd, commands) 287 if transmit: 288 cmd = "lldp transmit" 289 add_command_to_config_list(interface, cmd, commands) 290 elif transmit is False: 291 cmd = "no lldp transmit" 292 add_command_to_config_list(interface, cmd, commands) 293 294 if med_tlv_select: 295 med_tlv_select = dict(med_tlv_select) 296 if med_tlv_select.get("inventory_management"): 297 add_command_to_config_list( 298 interface, 299 "lldp med-tlv-select inventory-management", 300 commands, 301 ) 302 if tlv_select: 303 tlv_select = dict(tlv_select) 304 if tlv_select.get("power_management"): 305 add_command_to_config_list( 306 interface, "lldp tlv-select power-management", commands 307 ) 308 309 return commands 310 311 def _clear_config(self, want, have): 312 # Delete the interface config based on the want and have config 313 commands = [] 314 if want.get("name"): 315 interface = "interface " + want["name"] 316 else: 317 interface = "interface " + have["name"] 318 319 if have.get("receive") and have.get("receive") != want.get("receive"): 320 cmd = "lldp receive" 321 remove_command_from_config_list(interface, cmd, commands) 322 if have.get("transmit") and have.get("transmit") != want.get( 323 "transmit" 324 ): 325 cmd = "lldp transmit" 326 remove_command_from_config_list(interface, cmd, commands) 327 328 return commands 329