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 iosxr_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""" 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 search_obj_in_list, 25 dict_diff, 26 remove_empties, 27) 28from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.facts.facts import ( 29 Facts, 30) 31from ansible.module_utils.six import iteritems 32from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.utils.utils import ( 33 dict_delete, 34 pad_commands, 35 flatten_dict, 36) 37 38 39class Lldp_interfaces(ConfigBase): 40 """ 41 The iosxr_lldp_interfaces class 42 """ 43 44 gather_subset = ["!all", "!min"] 45 46 gather_network_resources = ["lldp_interfaces"] 47 48 def __init__(self, module): 49 super(Lldp_interfaces, self).__init__(module) 50 51 def get_lldp_interfaces_facts(self, data=None): 52 """ Get the 'facts' (the current configuration) 53 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 70 :rtype: A dictionary 71 :returns: The result from module execution 72 """ 73 result = {"changed": False} 74 warnings = list() 75 commands = list() 76 77 if self.state in self.ACTION_STATES: 78 existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() 79 else: 80 existing_lldp_interfaces_facts = [] 81 82 if self.state in self.ACTION_STATES or self.state == "rendered": 83 commands.extend(self.set_config(existing_lldp_interfaces_facts)) 84 85 if commands and self.state in self.ACTION_STATES: 86 if not self._module.check_mode: 87 self._connection.edit_config(commands) 88 result["changed"] = True 89 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 98 elif self.state == "parsed": 99 running_config = self._module.params["running_config"] 100 if not running_config: 101 self._module.fail_json( 102 msg="value of running_config parameter must not be empty for state parsed" 103 ) 104 result["parsed"] = self.get_lldp_interfaces_facts( 105 data=running_config 106 ) 107 108 if self.state in self.ACTION_STATES: 109 result["before"] = existing_lldp_interfaces_facts 110 if result["changed"]: 111 result["after"] = changed_lldp_interfaces_facts 112 elif self.state == "gathered": 113 result["gathered"] = changed_lldp_interfaces_facts 114 115 result["warnings"] = warnings 116 return result 117 118 def set_config(self, existing_lldp_interfaces_facts): 119 """ Collect the configuration from the args passed to the module, 120 collect the current configuration (as a dict from facts) 121 122 :rtype: A list 123 :returns: the commands necessary to migrate the current configuration 124 to the desired configuration 125 """ 126 want = self._module.params["config"] 127 have = existing_lldp_interfaces_facts 128 resp = self.set_state(want, have) 129 return to_list(resp) 130 131 def set_state(self, want, have): 132 """ Select the appropriate function based on the state provided 133 134 :param want: the desired configuration as a dictionary 135 :param have: the current configuration as a dictionary 136 :rtype: A list 137 :returns: the commands necessary to migrate the current configuration 138 to the desired configuration 139 """ 140 state = self._module.params["state"] 141 commands = [] 142 if ( 143 state in ("overridden", "merged", "replaced", "rendered") 144 and not want 145 ): 146 self._module.fail_json( 147 msg="value of config parameter must not be empty for state {0}".format( 148 state 149 ) 150 ) 151 152 if state == "overridden": 153 commands.extend(self._state_overridden(want, have)) 154 155 elif state == "deleted": 156 if not want: 157 for intf in have: 158 commands.extend( 159 self._state_deleted({"name": intf["name"]}, intf) 160 ) 161 else: 162 for item in want: 163 obj_in_have = search_obj_in_list(item["name"], have) 164 commands.extend(self._state_deleted(item, obj_in_have)) 165 166 else: 167 for item in want: 168 name = item["name"] 169 obj_in_have = search_obj_in_list(name, have) 170 171 if state in ("merged", "rendered"): 172 commands.extend(self._state_merged(item, obj_in_have)) 173 174 elif state == "replaced": 175 commands.extend(self._state_replaced(item, obj_in_have)) 176 177 return commands 178 179 def _state_replaced(self, want, have): 180 """ The command generator when state is replaced 181 182 :rtype: A list 183 :returns: the commands necessary to migrate the current configuration 184 to the desired configuration 185 """ 186 commands = [] 187 replaced_commands = [] 188 merged_commands = [] 189 190 if have: 191 replaced_commands = self._state_deleted(want, have) 192 193 merged_commands = self._state_merged(want, have) 194 195 if merged_commands and replaced_commands: 196 del merged_commands[0] 197 198 commands.extend(replaced_commands) 199 commands.extend(merged_commands) 200 201 return commands 202 203 def _state_overridden(self, want, have): 204 """ The command generator when state is overridden 205 206 :rtype: A list 207 :returns: the commands necessary to migrate the current configuration 208 to the desired configuration 209 """ 210 commands = [] 211 212 for intf in have: 213 intf_in_want = search_obj_in_list(intf["name"], want) 214 if not intf_in_want: 215 commands.extend( 216 self._state_deleted({"name": intf["name"]}, intf) 217 ) 218 219 for intf in want: 220 intf_in_have = search_obj_in_list(intf["name"], have) 221 commands.extend(self._state_replaced(intf, intf_in_have)) 222 223 return commands 224 225 def _state_merged(self, want, have): 226 """ The command generator when state is merged 227 228 :rtype: A list 229 :returns: the commands necessary to merge the provided into 230 the current configuration 231 """ 232 commands = [] 233 if not have: 234 have = {"name": want["name"]} 235 236 for key, value in iteritems( 237 flatten_dict(remove_empties(dict_diff(have, want))) 238 ): 239 commands.append(self._compute_commands(key, value)) 240 241 if commands: 242 pad_commands(commands, want["name"]) 243 244 return commands 245 246 def _state_deleted(self, want, have): 247 """ The command generator when state is deleted 248 249 :rtype: A list 250 :returns: the commands necessary to remove the current configuration 251 of the provided objects 252 """ 253 commands = [] 254 255 for key, value in iteritems( 256 flatten_dict(dict_delete(have, remove_empties(want))) 257 ): 258 commands.append(self._compute_commands(key, value, remove=True)) 259 260 if commands: 261 pad_commands(commands, have["name"]) 262 263 return commands 264 265 def _compute_commands(self, key, value=None, remove=False): 266 if key == "mac_address": 267 cmd = "lldp destination mac-address {0}".format(value) 268 if remove: 269 return "no {0}".format(cmd) 270 else: 271 return cmd 272 273 else: 274 cmd = "lldp {0} disable".format(key) 275 if not value and not remove: 276 return cmd 277 elif (value and not remove) or (not value and remove): 278 return "no {0}".format(cmd) 279