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 cisco.ios_bgp_address_family 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 ansible.module_utils.six import iteritems 21from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( 22 dict_merge, 23) 24from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.resource_module import ( 25 ResourceModule, 26) 27from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import ( 28 Facts, 29) 30from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.bgp_address_family import ( 31 Bgp_address_familyTemplate, 32) 33 34 35class Bgp_address_family(ResourceModule): 36 """ 37 The cisco.ios_bgp_address_family config class 38 """ 39 40 gather_subset = ["!all", "!min"] 41 42 parsers = [ 43 "as_number", 44 "afi", 45 "default", 46 "default_metric", 47 "distance", 48 "table_map", 49 ] 50 51 def __init__(self, module): 52 super(Bgp_address_family, self).__init__( 53 empty_fact_val={}, 54 facts_module=Facts(module), 55 module=module, 56 resource="bgp_address_family", 57 tmplt=Bgp_address_familyTemplate(), 58 ) 59 60 def execute_module(self): 61 """ Execute the module 62 63 :rtype: A dictionary 64 :returns: The result from module execution 65 """ 66 if self.state not in ["parsed", "gathered"]: 67 self.generate_commands() 68 self.run_commands() 69 return self.result 70 71 def generate_commands(self): 72 """ Generate configuration commands to send based on 73 want, have and desired state. 74 """ 75 if self.want: 76 wantd = {self.want["as_number"]: self.want} 77 else: 78 wantd = dict() 79 if self.have: 80 haved = {self.have["as_number"]: self.have} 81 else: 82 haved = dict() 83 84 for each in wantd, haved: 85 self.list_to_dict(each) 86 87 # if state is merged, merge want onto have and then compare 88 if self.state == "merged": 89 wantd = dict_merge(haved, wantd) 90 if len(wantd) > 1: 91 self._module.fail_json( 92 msg="BGP is already running. Only one BGP instance is allowed per device." 93 ) 94 # if state is deleted, empty out wantd and set haved to wantd 95 if self.state == "deleted": 96 as_number = None 97 if wantd: 98 temp = dict() 99 for key, val in iteritems(haved): 100 as_number = key 101 if wantd.get(key): 102 for k, v in iteritems(val.get("address_family")): 103 w = wantd[key].get("address_family") 104 if k in w: 105 temp.update({k: v}) 106 val["address_family"] = temp 107 elif haved: 108 as_number = list(haved)[0] 109 temp = {} 110 for k, v in iteritems(haved): 111 if not wantd: 112 temp.update({k: v}) 113 haved = temp 114 wantd = dict() 115 for k, have in iteritems(haved): 116 if k not in wantd: 117 self._compare(want=wantd, have=have, as_number=as_number) 118 119 # remove superfluous config for overridden 120 if self.state == "overridden": 121 for key, have in iteritems(haved): 122 if wantd.get(key): 123 if have.get("address_family"): 124 for k, v in iteritems(have.get("address_family")): 125 w = wantd[key].get("address_family") 126 if k not in w: 127 self._compare( 128 want=dict(), 129 have={"address_family": {k: v}}, 130 as_number=key, 131 ) 132 133 for k, want in iteritems(wantd): 134 self._compare(want=want, have=haved.pop(k, dict()), as_number=k) 135 136 def _compare(self, want, have, as_number): 137 """Leverages the base class `compare()` method and 138 populates the list of commands to be run by comparing 139 the `want` and `have` data with the `parsers` defined 140 for the Bgp_address_family network resource. 141 """ 142 if want != have and self.state != "deleted": 143 self._compare_af(want, have) 144 elif self.state == "deleted": 145 self._delete_af(have) 146 147 if self.commands and "router bgp" not in self.commands[0]: 148 self.commands.insert(0, "router bgp {0}".format(as_number)) 149 150 def _compare_af(self, want, have): 151 w = want.get("address_family", dict()) 152 h = have.get("address_family", dict()) 153 154 for key, val in iteritems(w): 155 cmd_len = len(self.commands) 156 if h.get(key): 157 h_key = h.get(key) 158 else: 159 h_key = dict() 160 if val != h_key: 161 self._aggregate_address_af_config_compare(val, have=h_key) 162 self._bgp_af_config_compare(val, have=h_key) 163 self._compare_neighbor(val, have=h_key) 164 self._compare_network(val, have=h_key) 165 self._compare_snmp(val, have=h_key) 166 self.compare( 167 parsers=self.parsers, want=val, have=h.pop(key, dict()) 168 ) 169 if cmd_len != len(self.commands): 170 af_cmd = "address-family {afi}".format(**val) 171 if val.get("safi"): 172 af_cmd += " {safi}".format(**val) 173 if val.get("vrf"): 174 af_cmd += " vrf {vrf}".format(**val) 175 self.commands.insert(cmd_len, af_cmd) 176 if not w and self.state == "overridden": 177 self._delete_af(have) 178 179 def _aggregate_address_af_config_compare(self, want, have): 180 parsers = ["aggregate_address"] 181 w_aggregate_address = want.get("aggregate_address", {}) 182 if have: 183 h_aggregate_address = have.get("aggregate_address", {}) 184 else: 185 h_aggregate_address = {} 186 for key, val in iteritems(w_aggregate_address): 187 if h_aggregate_address and h_aggregate_address.get(key): 188 self.compare( 189 parsers=parsers, 190 want={"aggregate_address": val}, 191 have={"aggregate_address": h_aggregate_address.pop(key)}, 192 ) 193 else: 194 self.compare( 195 parsers=parsers, 196 want={"aggregate_address": val}, 197 have=dict(), 198 ) 199 if self.state == "replaced" or self.state == "overridden": 200 if h_aggregate_address: 201 for key, val in iteritems(h_aggregate_address): 202 self.compare( 203 parsers=parsers, 204 want=dict(), 205 have={"aggregate_address": val}, 206 ) 207 elif have.get("aggregate_address"): 208 for key, val in iteritems(have.pop("aggregate_address")): 209 self.compare( 210 parsers=parsers, 211 want=dict(), 212 have={"aggregate_address": val}, 213 ) 214 215 def _bgp_af_config_compare(self, want, have): 216 parsers = [ 217 "bgp.additional_paths", 218 "bgp.dampening", 219 "bgp.config", 220 "bgp.slow_peer", 221 ] 222 w_bgp = want.get("bgp", dict()) 223 if have: 224 h_bgp = have.get("bgp", dict()) 225 else: 226 h_bgp = dict() 227 for key, val in iteritems(w_bgp): 228 if h_bgp and h_bgp.get(key): 229 self.compare( 230 parsers=parsers, 231 want={"bgp": {key: val}}, 232 have={"bgp": {key: h_bgp.pop(key)}}, 233 ) 234 else: 235 self.compare( 236 parsers=parsers, want={"bgp": {key: val}}, have=dict() 237 ) 238 if self.state == "replaced" or self.state == "overridden": 239 if h_bgp: 240 for key, val in iteritems(h_bgp): 241 self.compare( 242 parsers=parsers, want=dict(), have={"bgp": {key: val}} 243 ) 244 elif have.get("bgp"): 245 for key, val in iteritems(have.pop("bgp")): 246 self.compare( 247 parsers=parsers, want=dict(), have={"bgp": {key: val}} 248 ) 249 250 def _compare_neighbor(self, want, have): 251 parsers = [ 252 "neighbor", 253 "neighbor.prefix_lists", 254 "neighbor.route_maps", 255 "neighbor.slow_peer", 256 ] 257 neighbor_key = ["address", "ipv6_address", "tag"] 258 deprecated = False 259 w = want.get("neighbor", {}) if want else {} 260 if have: 261 h = have.get("neighbor", {}) 262 else: 263 h = {} 264 265 def _handle_neighbor_deprecated(w_key, h_key, want, have): 266 # function to handle idempotency, when deprecated params are present 267 # in want and their equivalent supported param are present inside have 268 keys = w_key + "s" 269 if have[h_key].get(keys): 270 temp_have = have[h_key][keys] 271 if want["name"] in temp_have: 272 param_name = want["name"] 273 if have[h_key][keys][param_name] == want: 274 k = keys 275 v = {param_name: want} 276 deprecated = True 277 return k, v, deprecated 278 279 for key, val in iteritems(w): 280 val = self.handle_deprecated(val) 281 if h and h.get(key): 282 neighbor_tag = [each for each in neighbor_key if each in val][ 283 0 284 ] 285 for k, v in iteritems(val): 286 if k == "route_map" or k == "prefix_list": 287 k, v, deprecated = _handle_neighbor_deprecated( 288 k, key, v, h 289 ) 290 if h[key].get(k) and k not in neighbor_key: 291 if k not in ["prefix_lists", "route_maps"]: 292 self.compare( 293 parsers=parsers, 294 want={ 295 "neighbor": { 296 neighbor_tag: val[neighbor_tag], 297 k: v, 298 } 299 }, 300 have={ 301 "neighbor": { 302 neighbor_tag: val[neighbor_tag], 303 k: h[key].pop(k, {}), 304 } 305 }, 306 ) 307 if k in ["prefix_lists", "route_maps"]: 308 for k_param, v_param in iteritems(val[k]): 309 self.compare( 310 parsers=parsers, 311 want={ 312 "neighbor": { 313 neighbor_tag: val[neighbor_tag], 314 k: v_param, 315 } 316 }, 317 have={ 318 "neighbor": { 319 neighbor_tag: val[neighbor_tag], 320 k: h[key][k].pop(k_param, {}), 321 } 322 }, 323 ) 324 elif k not in neighbor_key: 325 if k not in ["prefix_lists", "route_maps"]: 326 self.compare( 327 parsers=parsers, 328 want={ 329 "neighbor": { 330 neighbor_tag: val[neighbor_tag], 331 k: v, 332 } 333 }, 334 have=dict(), 335 ) 336 elif ( 337 k in ["prefix_lists", "route_maps"] 338 and not deprecated 339 ): 340 for k_param, v_param in iteritems(val[k]): 341 self.compare( 342 parsers=parsers, 343 want={ 344 "neighbor": { 345 neighbor_tag: val[neighbor_tag], 346 k: v_param, 347 } 348 }, 349 have=dict(), 350 ) 351 else: 352 self.compare( 353 parsers=parsers, want={"neighbor": val}, have=dict() 354 ) 355 for param in ["prefix_lists", "route_maps"]: 356 if param in val: 357 for k_param, v_param in iteritems(val[param]): 358 self.compare( 359 parsers=parsers, 360 want={ 361 "neighbor": { 362 "address": val["address"], 363 param: v_param, 364 } 365 }, 366 have=dict(), 367 ) 368 val.pop(param) 369 if self.state == "replaced" or self.state == "overridden": 370 for key, val in iteritems(h): 371 self.compare( 372 parsers=parsers, want=dict(), have={"neighbor": val} 373 ) 374 count = 0 375 remote = None 376 activate = None 377 for each in self.commands: 378 if "no" in each and "remote-as" in each: 379 remote = count 380 if "no" in each and "activate" in each: 381 activate = count 382 count += 1 383 if activate and activate > remote: 384 if count > 0 or "activate" in self.commands[activate]: 385 self.commands.append(self.commands.pop(activate)) 386 if remote and activate > remote: 387 if count > 0 or "remote-as" in self.commands[remote]: 388 self.commands.append(self.commands.pop(remote)) 389 390 def _compare_network(self, want, have): 391 parsers = ["network"] 392 w = want.get("network", dict()) 393 h = have.get("network", dict()) 394 for key, val in iteritems(w): 395 if h and h.get(key): 396 h_network = h.pop(key) 397 if h_network != val: 398 self.compare( 399 parsers=parsers, 400 want={"network": val}, 401 have={"network": val}, 402 ) 403 else: 404 self.compare( 405 parsers=parsers, want={"network": val}, have=dict() 406 ) 407 if self.state == "replaced" or self.state == "overridden": 408 for key, val in iteritems(h): 409 self.compare( 410 parsers=parsers, want=dict(), have={"network": val} 411 ) 412 413 def _compare_snmp(self, want, have): 414 parsers = ["snmp"] 415 w = want.get("snmp", dict()) 416 h = have.get("snmp", dict()) 417 if w: 418 for key, val in iteritems(w["context"]): 419 if h: 420 h_snmp_param = h["context"].pop(key) 421 if h_snmp_param and key != "name": 422 self.compare( 423 parsers=parsers, 424 want={ 425 "snmp": { 426 key: val, 427 "name": w["context"]["name"], 428 } 429 }, 430 have={ 431 "snmp": { 432 key: h_snmp_param, 433 "name": w["context"]["name"], 434 } 435 }, 436 ) 437 elif key == "community" or key == "user": 438 self.compare( 439 parsers=parsers, 440 want={ 441 "snmp": {key: val, "name": w["context"]["name"]} 442 }, 443 have=dict(), 444 ) 445 elif h and self.state == "replaced" or self.state == "overridden": 446 for key, val in iteritems(h): 447 self.compare( 448 parsers=parsers, 449 want=dict(), 450 have={"snmp": {key: val, "name": h["context"]["name"]}}, 451 ) 452 453 def _delete_af(self, have): 454 h = have.get("address_family", dict()) 455 for key, val in iteritems(h): 456 if "safi" in val and "vrf" in val: 457 cmd = "no address-family {afi} {safi} vrf {vrf}".format(**val) 458 elif "safi" in val: 459 cmd = "no address-family {afi} {safi}".format(**val) 460 else: 461 cmd = "no address-family {afi}".format(**val) 462 self.commands.append(cmd) 463 464 def list_to_dict(self, param): 465 if param: 466 for key, val in iteritems(param): 467 if val.get("address_family"): 468 temp = {} 469 for each in val.get("address_family", []): 470 temp.update( 471 { 472 each["afi"] 473 + "_" 474 + each.get("safi", "") 475 + "_" 476 + each.get("vrf", ""): each 477 } 478 ) 479 val["address_family"] = temp 480 self.list_to_dict(val["address_family"]) 481 if "aggregate_address" in val: 482 temp = {} 483 for each in val["aggregate_address"]: 484 temp.update({each["address"]: each}) 485 val["aggregate_address"] = temp 486 if "bgp" in val and "slow_peer" in val["bgp"]: 487 temp = {} 488 for each in val["bgp"]["slow_peer"]: 489 temp.update({list(each)[0]: each[list(each)[0]]}) 490 val["bgp"]["slow_peer"] = temp 491 if "neighbor" in val: 492 for each in val["neighbor"]: 493 if each.get("prefix_lists"): 494 temp = {} 495 for every in each["prefix_lists"]: 496 temp.update({every["name"]: every}) 497 each["prefix_lists"] = temp 498 if each.get("route_maps"): 499 temp = {} 500 for every in each["route_maps"]: 501 temp.update({every["name"]: every}) 502 each["route_maps"] = temp 503 if each.get("slow_peer"): 504 each["slow_peer"] = { 505 list(every)[0]: every[list(every)[0]] 506 for every in each["slow_peer"] 507 } 508 val["neighbor"] = { 509 each.get("address") 510 or each.get("ipv6_address") 511 or each.get("tag"): each 512 for each in val.get("neighbor", []) 513 } 514 if "network" in val: 515 temp = {} 516 for each in val.get("network", []): 517 temp.update({each["address"]: each}) 518 val["network"] = temp 519 520 def handle_deprecated(self, want_to_validate): 521 if want_to_validate.get("next_hop_self") and want_to_validate.get( 522 "nexthop_self" 523 ): 524 del want_to_validate["next_hop_self"] 525 elif want_to_validate.get("next_hop_self"): 526 del want_to_validate["next_hop_self"] 527 want_to_validate["nexthop_self"] = {"all": True} 528 return want_to_validate 529