1# 2# -*- coding: utf-8 -*- 3# Copyright 2020 Red Hat 4# GNU General Public License v3.0+ 5# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6# 7 8from __future__ import absolute_import, division, print_function 9 10__metaclass__ = type 11 12""" 13The nxos_ospfv3 config file. 14It is in this file where the current configuration (as dict) 15is compared to the provided configuration (as dict) and the command set 16necessary to bring the current configuration to its desired end-state is 17created. 18""" 19 20from copy import deepcopy 21 22from ansible.module_utils.six import iteritems 23from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( 24 dict_merge, 25 get_from_dict, 26) 27from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.resource_module import ( 28 ResourceModule, 29) 30from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import ( 31 Facts, 32) 33from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv3 import ( 34 Ospfv3Template, 35) 36 37 38class Ospfv3(ResourceModule): 39 """ 40 The nxos_ospfv3 config class 41 """ 42 43 def __init__(self, module): 44 super(Ospfv3, self).__init__( 45 empty_fact_val={}, 46 facts_module=Facts(module), 47 module=module, 48 resource="ospfv3", 49 tmplt=Ospfv3Template(), 50 ) 51 self.parsers = [ 52 "auto_cost", 53 "flush_routes", 54 "graceful_restart.set", 55 "graceful_restart.helper_disable", 56 "graceful_restart.grace_period", 57 "graceful_restart.planned_only", 58 "isolate", 59 "log_adjacency_changes", 60 "max_lsa", 61 "max_metric", 62 "name_lookup", 63 "passive_interface.default", 64 "router_id", 65 "shutdown", 66 "timers.lsa_arrival", 67 "timers.lsa_group_pacing", 68 "timers.throttle.lsa", 69 ] 70 71 def execute_module(self): 72 """ Execute the module 73 74 :rtype: A dictionary 75 :returns: The result from module execution 76 """ 77 if self.state not in ["parsed", "gathered"]: 78 self.generate_commands() 79 self.run_commands() 80 return self.result 81 82 def generate_commands(self): 83 """ Generate configuration commands to send based on 84 want, have and desired state. 85 """ 86 wantd = { 87 (entry["process_id"]): entry 88 for entry in self.want.get("processes", []) 89 } 90 haved = { 91 (entry["process_id"]): entry 92 for entry in self.have.get("processes", []) 93 } 94 95 # turn all lists of dicts into dicts prior to merge 96 for entry in wantd, haved: 97 self._ospfv3_list_to_dict(entry) 98 99 # if state is merged, merge want onto have and then compare 100 if self.state == "merged": 101 wantd = dict_merge(haved, wantd) 102 103 # if state is deleted, empty out wantd and set haved to wantd 104 if self.state == "deleted": 105 haved = { 106 k: v for k, v in iteritems(haved) if k in wantd or not wantd 107 } 108 wantd = {} 109 110 # if state is overridden, first remove processes that are in have but not in want 111 if self.state in ["overridden", "deleted"]: 112 for k, have in iteritems(haved): 113 if k not in wantd: 114 self.addcmd(have, "process_id", True) 115 116 for k, want in iteritems(wantd): 117 self._compare(want=want, have=haved.pop(k, {})) 118 119 def _compare(self, want, have): 120 """Leverages the base class `compare()` method and 121 populates the list of commands to be run by comparing 122 the `want` and `have` data with the `parsers` defined 123 for the Ospfv3 network resource. 124 """ 125 begin = len(self.commands) 126 self.compare(parsers=self.parsers, want=want, have=have) 127 self._areas_compare(want=want, have=have) 128 self._vrfs_compare(want=want, have=have) 129 self._af_compare(want=want, have=have) 130 131 if len(self.commands) != begin or (not have and want): 132 self.commands.insert( 133 begin, 134 self._tmplt.render( 135 want or have, 136 "vrf" 137 if "vrf" in (want.keys() or have.keys()) 138 else "process_id", 139 False, 140 ), 141 ) 142 143 def _areas_compare(self, want, have): 144 wareas = want.get("areas", {}) 145 hareas = have.get("areas", {}) 146 for name, entry in iteritems(wareas): 147 self._area_compare(want=entry, have=hareas.pop(name, {})) 148 for name, entry in iteritems(hareas): 149 self._area_compare(want={}, have=entry) 150 151 def _area_compare(self, want, have): 152 parsers = ["area.nssa", "area.nssa.translate", "area.stub"] 153 self.compare(parsers=parsers, want=want, have=have) 154 155 def _vrfs_compare(self, want, have): 156 wvrfs = want.get("vrfs", {}) 157 hvrfs = have.get("vrfs", {}) 158 for name, entry in iteritems(wvrfs): 159 self._compare(want=entry, have=hvrfs.pop(name, {})) 160 # remove remaining items in have for replaced 161 for name, entry in iteritems(hvrfs): 162 self.addcmd(entry, "vrf", True) 163 164 def _af_compare(self, want, have): 165 parsers = [ 166 "default_information.originate", 167 "distance", 168 "maximum_paths", 169 "table_map", 170 "timers.throttle.spf", 171 ] 172 waf = want.get("address_family", {}) 173 haf = have.get("address_family", {}) 174 175 cmd_ptr = len(self.commands) 176 177 self._af_areas_compare(want=waf, have=haf) 178 self._af_compare_lists(want=waf, have=haf) 179 self.compare(parsers=parsers, want=waf, have=haf) 180 181 cmd_ptr_nxt = len(self.commands) 182 if cmd_ptr < cmd_ptr_nxt: 183 self.commands.insert(cmd_ptr, "address-family ipv6 unicast") 184 185 def _af_areas_compare(self, want, have): 186 wareas = want.get("areas", {}) 187 hareas = have.get("areas", {}) 188 for name, entry in iteritems(wareas): 189 self._af_area_compare(want=entry, have=hareas.pop(name, {})) 190 for name, entry in iteritems(hareas): 191 self._af_area_compare(want={}, have=entry) 192 193 def _af_area_compare(self, want, have): 194 self.compare(parsers=["area.default_cost"], want=want, have=have) 195 self._af_area_compare_lists(want=want, have=have) 196 197 def _af_area_compare_lists(self, want, have): 198 for attrib in ["filter_list", "ranges"]: 199 wdict = want.get(attrib, {}) 200 hdict = have.get(attrib, {}) 201 for key, entry in iteritems(wdict): 202 if entry != hdict.pop(key, {}): 203 entry["area_id"] = want["area_id"] 204 self.addcmd(entry, "area.{0}".format(attrib), False) 205 # remove remaining items in have for replaced 206 for entry in hdict.values(): 207 entry["area_id"] = have["area_id"] 208 self.addcmd(entry, "area.{0}".format(attrib), True) 209 210 def _af_compare_lists(self, want, have): 211 for attrib in ["summary_address", "redistribute"]: 212 wdict = get_from_dict(want, attrib) or {} 213 hdict = get_from_dict(have, attrib) or {} 214 215 for key, entry in iteritems(wdict): 216 if entry != hdict.pop(key, {}): 217 self.addcmd(entry, attrib, False) 218 # remove remaining items in have for replaced 219 for entry in hdict.values(): 220 self.addcmd(entry, attrib, True) 221 222 def _ospfv3_list_to_dict(self, entry): 223 for _pid, proc in iteritems(entry): 224 proc["areas"] = { 225 entry["area_id"]: entry for entry in proc.get("areas", []) 226 } 227 af = proc.get("address_family") 228 if af: 229 for area in af.get("areas", []): 230 area["ranges"] = { 231 entry["prefix"]: entry 232 for entry in area.get("ranges", []) 233 } 234 area["filter_list"] = { 235 entry["direction"]: entry 236 for entry in area.get("filter_list", []) 237 } 238 af["areas"] = { 239 entry["area_id"]: entry for entry in af.get("areas", []) 240 } 241 af["summary_address"] = { 242 entry["prefix"]: entry 243 for entry in af.get("summary_address", []) 244 } 245 af["redistribute"] = { 246 (entry.get("id"), entry["protocol"]): entry 247 for entry in af.get("redistribute", []) 248 } 249 if "vrfs" in proc: 250 proc["vrfs"] = { 251 entry["vrf"]: entry for entry in proc.get("vrfs", []) 252 } 253 self._ospfv3_list_to_dict(proc["vrfs"]) 254