Skip to content

Commit

Permalink
[prefix_lists] fix delete state and optimise code (#916)
Browse files Browse the repository at this point in the history
* facts fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* add config code

* cleanup

* cleanup again

* sanity fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix tests

* fix integration tests

* remove extra test

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
KB-perByte and pre-commit-ci[bot] authored Aug 27, 2023
1 parent cdc4ceb commit d9f5cda
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 277 deletions.
3 changes: 3 additions & 0 deletions changelogs/fragments/prefix_list_fix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
bugfixes:
- ios_prefix_lists - fix deleted state to remove exisiting prefix lists from configuration.
1 change: 1 addition & 0 deletions docs/cisco.ios.ios_prefix_lists_module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1137,4 +1137,5 @@ Status
Authors
~~~~~~~

- Sagar Paul (@KB-perByte)
- Sumit Jaiswal (@justjais)
216 changes: 56 additions & 160 deletions plugins/module_utils/network/ios/config/prefix_lists/prefix_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,188 +63,84 @@ def generate_commands(self):
want, have and desired state.
"""
wantd = {entry["afi"]: entry for entry in self.want}

haved = {entry["afi"]: entry for entry in self.have}

# Convert each of config list to dict
for each in wantd, haved:
self.list_to_dict(each)
self._prefix_list_transform(wantd)
self._prefix_list_transform(haved)

# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)

# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
temp = None
for k, v in iteritems(haved):
if k in wantd:
if wantd[k].get("prefix_lists"):
want_afi_name = wantd[k].get("prefix_lists", {})
haved[k]["prefix_lists"] = {
key: val
for key, val in iteritems(v.get("prefix_lists"))
if key in want_afi_name
}
elif wantd:
temp = k
if temp:
haved.pop(k)
wantd = {}
for k, have in iteritems(haved):
for key, val in iteritems(have["prefix_lists"]):
if k == "ipv4":
k = "ip"
self.commands.append("no {0} prefix-list {1}".format(k, key))
haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd}
for key, hvalue in iteritems(haved):
wvalue = wantd.pop(key, {})
if wvalue:
wplists = wvalue.get("prefix_lists", {})
hplists = hvalue.get("prefix_lists", {})
hvalue["prefix_lists"] = {
k: v for k, v in iteritems(hplists) if k in wplists or not wplists
}

# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
for k, have in iteritems(haved):
want_afi = wantd.get(k, {})
for key, val in iteritems(have["prefix_lists"]):
if k == "ipv4":
k = "ip"
if want_afi and key not in want_afi.get("prefix_lists"):
self.commands.append("no {0} prefix-list {1}".format(k, key))
if k not in wantd:
self._compare(want={}, have=have)

for k, want in iteritems(wantd):
self._compare(want=want, have=haved.pop(k, {}))
# alligning cmd with negate cmd 1st followed by config cmd
if self.state in ["overridden", "replaced"]:
self.commands = [each for each in self.commands if "no" in each] + [
each for each in self.commands if "no" not in each
]

def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Prefix_lists network resource.
"""
if want != have and self.state != "deleted":
for k, v in iteritems(want["prefix_lists"]):
if have.get("prefix_lists") and have["prefix_lists"].get(k):
have_prefix = have["prefix_lists"].pop(k, {})
for key, val in iteritems(v.get("entries")):
if have_prefix.get("entries"):
have_prefix_param = have_prefix["entries"].pop(key, {})
else:
have_prefix_param = None
if have_prefix.get("description"):
self.compare(
parsers=self.parsers,
want={
"afi": want["afi"],
"name": k,
"prefix_list": {"description": v["description"]},
},
have={
"afi": want["afi"],
"name": k,
"prefix_list": {"description": have_prefix.pop("description")},
},
)
if have_prefix_param and val != have_prefix_param:
if key == "description":
# Code snippet should be removed when Description param is removed from
# entries level as this supports deprecated level of Description
self.compare(
parsers=self.parsers,
want={"afi": want["afi"], "name": k, "prefix_list": {key: val}},
have={
"afi": have["afi"],
"name": k,
"prefix_list": {key: have_prefix_param},
},
)
else:
if self.state == "merged" and have_prefix_param.get(
"sequence",
) == val.get("sequence"):
self._module.fail_json(
"Cannot update existing sequence {0} of Prefix Lists {1} with state merged.".format(
val.get("sequence"),
k,
)
+ " Please use state replaced or overridden.",
)
self.compare(
parsers=self.parsers,
want=dict(),
have={
"afi": have["afi"],
"name": k,
"prefix_list": have_prefix_param,
},
)
self.compare(
parsers=self.parsers,
want={"afi": want["afi"], "name": k, "prefix_list": val},
have={
"afi": have["afi"],
"name": k,
"prefix_list": have_prefix_param,
},
)
elif val and val != have_prefix_param:
self.compare(
parsers=self.parsers,
want={"afi": want["afi"], "name": k, "prefix_list": val},
have=dict(),
)
if have_prefix and (self.state == "replaced" or self.state == "overridden"):
if have_prefix.get("description"):
# Code snippet should be removed when Description param is removed from
# entries level as this supports deprecated level of Description
self.compare(
parsers=self.parsers,
want=dict(),
have={
"afi": want["afi"],
"name": k,
"prefix_list": {"description": have_prefix["description"]},
},
)
for key, val in iteritems(have_prefix.get("entries")):
self.compare(
parsers=self.parsers,
want=dict(),
have={"afi": have["afi"], "name": k, "prefix_list": val},
)
elif v:
if v.get("description"):
self.compare(
parsers=self.parsers,
want={
"afi": want["afi"],
"name": k,
"prefix_list": {"description": v["description"]},
},
have=dict(),
)
for key, val in iteritems(v.get("entries")):
self.compare(
parsers=self.parsers,
want={"afi": want["afi"], "name": k, "prefix_list": val},
have=dict(),
)
wplists = want.get("prefix_lists", {})
hplists = have.get("prefix_lists", {})
for wk, wentry in iteritems(wplists):
hentry = hplists.pop(wk, {})
self.compare(["description"], want=wentry, have=hentry)
# compare sequences
self._compare_seqs(wentry.pop("entries", {}), hentry.pop("entries", {}))

def list_to_dict(self, param):
if param:
for key, val in iteritems(param):
if val.get("prefix_lists"):
temp_prefix_list = {}
for each in val["prefix_lists"]:
temp_entries = dict()
if each.get("entries"):
for every in each["entries"]:
temp_entries.update({str(every["sequence"]): every})
temp_prefix_list.update(
{
each["name"]: {
"description": each.get("description"),
"entries": temp_entries,
},
},
if self.state in ["overridden", "deleted"]:
# remove remaining prefix lists
for h in hplists.values():
self.commands.append(
"no {0} prefix-list {1}".format(h["afi"].replace("ipv4", "ip"), h["name"]),
)

def _compare_seqs(self, want, have):
for wseq, wentry in iteritems(want):
hentry = have.pop(wseq, {})
if hentry != wentry:
if hentry:
if self.state == "merged":
self._module.fail_json(
msg="Cannot update existing sequence {0} of prefix list {1} with state merged."
" Please use state replaced or overridden.".format(
hentry["sequence"],
hentry["name"],
),
)
val["prefix_lists"] = temp_prefix_list
else:
self.addcmd(hentry, "entry", negate=True)
self.addcmd(wentry, "entry")
# remove remaining entries from have prefix list
for hseq in have.values():
self.addcmd(hseq, "entry", negate=True)

def _prefix_list_transform(self, entry):
for afi, value in iteritems(entry):
if "prefix_lists" in value:
for plist in value["prefix_lists"]:
plist.update({"afi": afi})
if "entries" in plist:
for seq in plist["entries"]:
seq.update({"afi": afi, "name": plist["name"]})
plist["entries"] = {x["sequence"]: x for x in plist["entries"]}
value["prefix_lists"] = {entry["name"]: entry for entry in value["prefix_lists"]}
55 changes: 10 additions & 45 deletions plugins/module_utils/network/ios/facts/prefix_lists/prefix_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
based on the configuration.
"""

from copy import copy

from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils

from ansible_collections.cisco.ios.plugins.module_utils.network.ios.argspec.prefix_lists.prefix_lists import (
Expand Down Expand Up @@ -59,50 +56,18 @@ def populate_facts(self, connection, ansible_facts, data=None):
objs = prefix_lists_parser.parse()

final_objs = []
temp = {}
temp["afi"] = None
temp["prefix_lists"] = []

_prefix_list = {"ipv4": [], "ipv6": []}
if objs:
for k, v in iteritems(objs):
temp_prefix_list = {}
temp_prefix_list["entries"] = []
if not temp["afi"] or v["afi"] != temp["afi"]:
if temp and temp["afi"]:
temp["prefix_lists"] = sorted(
temp["prefix_lists"],
key=lambda k, sk="name": str(k[sk]),
)
# additional check for py3.5
if len(final_objs) == 2:
for each in final_objs:
if v["afi"] == each["afi"]:
each["prefix_lists"].extend(temp["prefix_lists"])
else:
final_objs.append(copy(temp))
temp["prefix_lists"] = []
temp["afi"] = v["afi"]
for each in v["prefix_lists"]:
if not temp_prefix_list.get("name"):
temp_prefix_list["name"] = each["name"]
if not temp_prefix_list.get("description") and each.get("description"):
temp_prefix_list["description"] = each["description"]
if each["entries"] and not each["entries"].get("description"):
temp_prefix_list["entries"].append(each["entries"])
temp["prefix_lists"].append(temp_prefix_list)
if temp and temp["afi"]:
temp["prefix_lists"] = sorted(
temp["prefix_lists"],
key=lambda k, sk="name": str(k[sk]),
for prefixes in list(objs.values()):
_afi = prefixes.pop("afi")
_prefix_list[_afi].append(
prefixes,
)
# additional check for py3.5
if len(final_objs) == 2:
for each in final_objs:
if v["afi"] == each["afi"]:
each["prefix_lists"].extend(temp["prefix_lists"])
else:
final_objs.append(copy(temp))

final_objs = sorted(final_objs, key=lambda k, sk="afi": k[sk])
if _prefix_list.get("ipv4"):
final_objs.append({"afi": "ipv4", "prefix_lists": _prefix_list.pop("ipv4")})
if _prefix_list.get("ipv6"):
final_objs.append({"afi": "ipv6", "prefix_lists": _prefix_list.pop("ipv6")})

ansible_facts["ansible_network_resources"].pop("prefix_lists", None)

Expand Down
Loading

0 comments on commit d9f5cda

Please sign in to comment.