Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: nxos_bgp_neighbor_af
extends_documentation_fragment:
- cisco.nxos.nxos
short_description: (deprecated, removed after 2023-02-24) Manages BGP address-family's neighbors configuration.
description:
- Manages BGP address-family's neighbors configurations on NX-OS switches.
version_added: 1.0.0
author: Gabriele Gerbino (@GGabriele)
deprecated:
alternative: nxos_bgp_neighbor_address_family
why: Updated module released with more functionality.
removed_at_date: '2023-02-24'
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- Unsupported for Cisco MDS
- C(state=absent) removes the whole BGP address-family's neighbor configuration.
- Default, when supported, removes properties
- In order to default maximum-prefix configuration, only C(max_prefix_limit=default)
is needed.
options:
asn:
description:
- BGP autonomous system number. Valid values are String, Integer in ASPLAIN or
ASDOT notation.
required: true
type: str
vrf:
description:
- Name of the VRF. The name 'default' is a valid VRF representing the global bgp.
default: default
type: str
neighbor:
description:
- Neighbor Identifier. Valid values are string. Neighbors may use IPv4 or IPv6
notation, with or without prefix length.
required: true
type: str
afi:
description:
- Address Family Identifier.
required: true
choices:
- ipv4
- ipv6
- vpnv4
- vpnv6
- l2vpn
type: str
safi:
description:
- Sub Address Family Identifier.
required: true
choices:
- unicast
- multicast
- evpn
type: str
additional_paths_receive:
description:
- Valid values are enable for basic command enablement; disable for disabling
the command at the neighbor af level (it adds the disable keyword to the basic
command); and inherit to remove the command at this level (the command value
is inherited from a higher BGP layer).
choices:
- enable
- disable
- inherit
type: str
additional_paths_send:
description:
- Valid values are enable for basic command enablement; disable for disabling
the command at the neighbor af level (it adds the disable keyword to the basic
command); and inherit to remove the command at this level (the command value
is inherited from a higher BGP layer).
choices:
- enable
- disable
- inherit
type: str
advertise_map_exist:
description:
- Conditional route advertisement. This property requires two route maps, an advertise-map
and an exist-map. Valid values are an array specifying both the advertise-map
name and the exist-map name, or simply 'default' e.g. ['my_advertise_map', 'my_exist_map'].
This command is mutually exclusive with the advertise_map_non_exist property.
type: list
elements: str
advertise_map_non_exist:
description:
- Conditional route advertisement. This property requires two route maps, an advertise-map
and an exist-map. Valid values are an array specifying both the advertise-map
name and the non-exist-map name, or simply 'default' e.g. ['my_advertise_map',
'my_non_exist_map']. This command is mutually exclusive with the advertise_map_exist
property.
type: list
elements: str
allowas_in:
description:
- Activate allowas-in property
type: bool
allowas_in_max:
description:
- Max-occurrences value for allowas_in. Valid values are an integer value or 'default'.
This is mutually exclusive with allowas_in.
type: str
as_override:
description:
- Activate the as-override feature.
type: bool
default_originate:
description:
- Activate the default-originate feature.
type: bool
default_originate_route_map:
description:
- Route-map for the default_originate property. Valid values are a string defining
a route-map name, or 'default'. This is mutually exclusive with default_originate.
type: str
disable_peer_as_check:
description:
- Disable checking of peer AS-number while advertising
type: bool
filter_list_in:
description:
- Valid values are a string defining a filter-list name, or 'default'.
type: str
filter_list_out:
description:
- Valid values are a string defining a filter-list name, or 'default'.
type: str
max_prefix_limit:
description:
- maximum-prefix limit value. Valid values are an integer value or 'default'.
type: str
max_prefix_interval:
description:
- Optional restart interval. Valid values are an integer. Requires max_prefix_limit.
May not be combined with max_prefix_warning.
type: str
max_prefix_threshold:
description:
- Optional threshold percentage at which to generate a warning. Valid values are
an integer value. Requires max_prefix_limit.
type: str
max_prefix_warning:
description:
- Optional warning-only keyword. Requires max_prefix_limit. May not be combined
with max_prefix_interval.
type: bool
next_hop_self:
description:
- Activate the next-hop-self feature.
type: bool
next_hop_third_party:
description:
- Activate the next-hop-third-party feature.
type: bool
prefix_list_in:
description:
- Valid values are a string defining a prefix-list name, or 'default'.
type: str
prefix_list_out:
description:
- Valid values are a string defining a prefix-list name, or 'default'.
type: str
route_map_in:
description:
- Valid values are a string defining a route-map name, or 'default'.
type: str
route_map_out:
description:
- Valid values are a string defining a route-map name, or 'default'.
type: str
route_reflector_client:
description:
- Router reflector client.
type: bool
send_community:
description:
- send-community attribute.
choices:
- none
- both
- extended
- standard
- default
type: str
soft_reconfiguration_in:
description:
- Valid values are 'enable' for basic command enablement; 'always' to add the
always keyword to the basic command; and 'inherit' to remove the command at
this level (the command value is inherited from a higher BGP layer).
choices:
- enable
- always
- inherit
type: str
soo:
description:
- Site-of-origin. Valid values are a string defining a VPN extcommunity or 'default'.
type: str
suppress_inactive:
description:
- suppress-inactive feature.
type: bool
unsuppress_map:
description:
- unsuppress-map. Valid values are a string defining a route-map name or 'default'.
type: str
weight:
description:
- Weight value. Valid values are an integer value or 'default'.
type: str
state:
description:
- Determines whether the config should be present or not on the device.
default: present
choices:
- present
- absent
type: str
rewrite_evpn_rt_asn:
description:
- Auto generate route targets for EBGP neighbor.
type: bool
version_added: 1.1.0
"""
EXAMPLES = """
- name: configure RR client
cisco.nxos.nxos_bgp_neighbor_af:
asn: 65535
neighbor: 192.0.2.3
afi: ipv4
safi: unicast
route_reflector_client: true
state: present
rewrite_evpn_rt_asn: true
"""
RETURN = """
commands:
description: commands sent to the device
returned: always
type: list
sample: ["router bgp 65535", "neighbor 192.0.2.3",
"address-family ipv4 unicast", "route-reflector-client",
"rewrite-evpn-rt-asn"]
"""
import re
from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
get_config,
load_config,
)
from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
nxos_argument_spec,
)
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
CustomNetworkConfig,
)
BOOL_PARAMS = [
"allowas_in",
"as_override",
"default_originate",
"disable_peer_as_check",
"next_hop_self",
"next_hop_third_party",
"route_reflector_client",
"suppress_inactive",
"rewrite_evpn_rt_asn",
]
PARAM_TO_COMMAND_KEYMAP = {
"afi": "address-family",
"asn": "router bgp",
"neighbor": "neighbor",
"additional_paths_receive": "capability additional-paths receive",
"additional_paths_send": "capability additional-paths send",
"advertise_map_exist": "advertise-map exist-map",
"advertise_map_non_exist": "advertise-map non-exist-map",
"allowas_in": "allowas-in",
"allowas_in_max": "allowas-in",
"as_override": "as-override",
"default_originate": "default-originate",
"default_originate_route_map": "default-originate route-map",
"disable_peer_as_check": "disable-peer-as-check",
"filter_list_in": "filter-list in",
"filter_list_out": "filter-list out",
"max_prefix_limit": "maximum-prefix",
"max_prefix_interval": "maximum-prefix interval",
"max_prefix_threshold": "maximum-prefix threshold",
"max_prefix_warning": "maximum-prefix warning",
"next_hop_self": "next-hop-self",
"next_hop_third_party": "next-hop-third-party",
"prefix_list_in": "prefix-list in",
"prefix_list_out": "prefix-list out",
"route_map_in": "route-map in",
"route_map_out": "route-map out",
"route_reflector_client": "route-reflector-client",
"safi": "address-family",
"send_community": "send-community",
"soft_reconfiguration_in": "soft-reconfiguration inbound",
"soo": "soo",
"suppress_inactive": "suppress-inactive",
"unsuppress_map": "unsuppress-map",
"weight": "weight",
"vrf": "vrf",
"rewrite_evpn_rt_asn": "rewrite-evpn-rt-asn",
}
def get_value(arg, config, module):
custom = [
"additional_paths_send",
"additional_paths_receive",
"max_prefix_limit",
"max_prefix_interval",
"max_prefix_threshold",
"max_prefix_warning",
"send_community",
"soft_reconfiguration_in",
]
command = PARAM_TO_COMMAND_KEYMAP[arg]
has_command = re.search(r"^\s+{0}\s*".format(command), config, re.M)
has_command_val = re.search(
r"(?:{0}\s)(?P<value>.*)$".format(command), config, re.M
)
value = ""
if arg in custom:
value = get_custom_value(arg, config, module)
elif arg == "next_hop_third_party":
has_no_command = re.search(
r"^\s+no\s+{0}\s*$".format(command), config, re.M
)
value = False
if not has_no_command:
value = True
elif arg in BOOL_PARAMS:
value = False
if has_command:
value = True
elif command.startswith("advertise-map"):
value = []
has_adv_map = re.search(
r"{0}\s(?P<value1>.*)\s{1}\s(?P<value2>.*)$".format(
*command.split()
),
config,
re.M,
)
if has_adv_map:
value = list(has_adv_map.groups())
elif command.split()[0] in ["filter-list", "prefix-list", "route-map"]:
has_cmd_direction_val = re.search(
r"{0}\s(?P<value>.*)\s{1}$".format(*command.split()), config, re.M
)
if has_cmd_direction_val:
value = has_cmd_direction_val.group("value")
elif has_command_val:
value = has_command_val.group("value")
return value
def get_custom_value(arg, config, module):
command = PARAM_TO_COMMAND_KEYMAP.get(arg)
splitted_config = config.splitlines()
value = ""
if arg.startswith("additional_paths"):
value = "inherit"
for line in splitted_config:
if command in line:
if "disable" in line:
value = "disable"
else:
value = "enable"
elif arg.startswith("max_prefix"):
for line in splitted_config:
if "maximum-prefix" in line:
splitted_line = line.split()
if arg == "max_prefix_limit":
value = splitted_line[1]
elif arg == "max_prefix_interval" and "restart" in line:
value = splitted_line[-1]
elif arg == "max_prefix_threshold" and len(splitted_line) > 2:
try:
int(splitted_line[2])
value = splitted_line[2]
except ValueError:
value = ""
elif arg == "max_prefix_warning":
value = "warning-only" in line
elif arg == "soft_reconfiguration_in":
value = "inherit"
for line in splitted_config:
if command in line:
if "always" in line:
value = "always"
else:
value = "enable"
elif arg == "send_community":
value = "none"
for line in splitted_config:
if command in line:
if "extended" in line:
if value == "standard":
value = "both"
else:
value = "extended"
elif "both" in line:
value = "both"
else:
value = "standard"
return value
def get_existing(module, args, warnings):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
asn_regex = re.compile(
r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S
)
match_asn = asn_regex.match(str(netcfg))
if match_asn:
existing_asn = match_asn.group("existing_asn")
parents = ["router bgp {0}".format(existing_asn)]
if module.params["vrf"] != "default":
parents.append("vrf {0}".format(module.params["vrf"]))
parents.append("neighbor {0}".format(module.params["neighbor"]))
parents.append(
"address-family {0} {1}".format(
module.params["afi"], module.params["safi"]
)
)
config = netcfg.get_section(parents)
if config:
for arg in args:
if arg not in ["asn", "vrf", "neighbor", "afi", "safi"]:
existing[arg] = get_value(arg, config, module)
existing["asn"] = existing_asn
existing["neighbor"] = module.params["neighbor"]
existing["vrf"] = module.params["vrf"]
existing["afi"] = module.params["afi"]
existing["safi"] = module.params["safi"]
else:
warnings.append(
"The BGP process didn't exist but the task just created it."
)
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key in table:
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = table.get(key)
return new_dict
def get_default_command(key, value, existing_commands):
command = ""
if existing_commands.get(key):
existing_value = existing_commands.get(key)
if value == "inherit":
if existing_value != "inherit":
command = "no {0}".format(key)
else:
if key == "advertise-map exist-map":
command = "no advertise-map {0} exist-map {1}".format(
existing_value[0], existing_value[1]
)
elif key == "advertise-map non-exist-map":
command = "no advertise-map {0} non-exist-map {1}".format(
existing_value[0], existing_value[1]
)
elif key == "filter-list in":
command = "no filter-list {0} in".format(existing_value)
elif key == "filter-list out":
command = "no filter-list {0} out".format(existing_value)
elif key == "prefix-list in":
command = "no prefix-list {0} in".format(existing_value)
elif key == "prefix-list out":
command = "no prefix-list {0} out".format(existing_value)
elif key == "route-map in":
command = "no route-map {0} in".format(existing_value)
elif key == "route-map out":
command = "no route-map {0} out".format(existing_value)
elif key.startswith("maximum-prefix"):
command = "no maximum-prefix"
elif key == "allowas-in max":
command = ["no allowas-in {0}".format(existing_value)]
command.append("allowas-in")
else:
command = "no {0} {1}".format(key, existing_value)
else:
if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS:
command = "no {0}".format(key)
return command
def fix_proposed(module, existing, proposed):
allowas_in = proposed.get("allowas_in")
allowas_in_max = proposed.get("allowas_in_max")
if allowas_in_max and not allowas_in:
proposed.pop("allowas_in_max")
elif allowas_in and allowas_in_max:
proposed.pop("allowas_in")
if (
existing.get("send_community") == "none"
and proposed.get("send_community") == "default"
):
proposed.pop("send_community")
return proposed
def state_present(module, existing, proposed, candidate):
commands = list()
proposed = fix_proposed(module, existing, proposed)
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in proposed_commands.items():
if value in ["inherit", "default"]:
command = get_default_command(key, value, existing_commands)
if isinstance(command, str):
if command and command not in commands:
commands.append(command)
elif isinstance(command, list):
for cmd in command:
if cmd not in commands:
commands.append(cmd)
elif key.startswith("maximum-prefix"):
if module.params["max_prefix_limit"] != "default":
command = "maximum-prefix {0}".format(
module.params["max_prefix_limit"]
)
if module.params["max_prefix_threshold"]:
command += " {0}".format(
module.params["max_prefix_threshold"]
)
if module.params["max_prefix_interval"]:
command += " restart {0}".format(
module.params["max_prefix_interval"]
)
elif module.params["max_prefix_warning"]:
command += " warning-only"
commands.append(command)
elif value is True:
commands.append(key)
elif value is False:
commands.append("no {0}".format(key))
elif key == "address-family":
commands.append(
"address-family {0} {1}".format(
module.params["afi"], module.params["safi"]
)
)
elif key.startswith("capability additional-paths"):
command = key
if value == "disable":
command += " disable"
commands.append(command)
elif key.startswith("advertise-map"):
direction = key.split()[1]
commands.append(
"advertise-map {1} {0} {2}".format(direction, *value)
)
elif key.split()[0] in ["filter-list", "prefix-list", "route-map"]:
commands.append("{1} {0} {2}".format(value, *key.split()))
elif key == "soft-reconfiguration inbound":
command = ""
if value == "enable":
command = key
elif value == "always":
command = "{0} {1}".format(key, value)
commands.append(command)
elif key == "send-community":
command = key
if value in ["standard", "extended"]:
commands.append("no " + key + " both")
command += " {0}".format(value)
commands.append(command)
else:
command = "{0} {1}".format(key, value)
commands.append(command)
if commands:
parents = ["router bgp {0}".format(module.params["asn"])]
if module.params["vrf"] != "default":
parents.append("vrf {0}".format(module.params["vrf"]))
parents.append("neighbor {0}".format(module.params["neighbor"]))
af_command = "address-family {0} {1}".format(
module.params["afi"], module.params["safi"]
)
parents.append(af_command)
if af_command in commands:
commands.remove(af_command)
candidate.add(commands, parents=parents)
def state_absent(module, existing, candidate):
commands = []
parents = ["router bgp {0}".format(module.params["asn"])]
if module.params["vrf"] != "default":
parents.append("vrf {0}".format(module.params["vrf"]))
parents.append("neighbor {0}".format(module.params["neighbor"]))
commands.append(
"no address-family {0} {1}".format(
module.params["afi"], module.params["safi"]
)
)
candidate.add(commands, parents=parents)
def main():
argument_spec = dict(
asn=dict(required=True, type="str"),
vrf=dict(required=False, type="str", default="default"),
neighbor=dict(required=True, type="str"),
afi=dict(
required=True,
type="str",
choices=["ipv4", "ipv6", "vpnv4", "vpnv6", "l2vpn"],
),
safi=dict(
required=True, type="str", choices=["unicast", "multicast", "evpn"]
),
additional_paths_receive=dict(
required=False,
type="str",
choices=["enable", "disable", "inherit"],
),
additional_paths_send=dict(
required=False,
type="str",
choices=["enable", "disable", "inherit"],
),
advertise_map_exist=dict(required=False, type="list", elements="str"),
advertise_map_non_exist=dict(
required=False, type="list", elements="str"
),
allowas_in=dict(required=False, type="bool"),
allowas_in_max=dict(required=False, type="str"),
as_override=dict(required=False, type="bool"),
default_originate=dict(required=False, type="bool"),
default_originate_route_map=dict(required=False, type="str"),
disable_peer_as_check=dict(required=False, type="bool"),
filter_list_in=dict(required=False, type="str"),
filter_list_out=dict(required=False, type="str"),
max_prefix_limit=dict(required=False, type="str"),
max_prefix_interval=dict(required=False, type="str"),
max_prefix_threshold=dict(required=False, type="str"),
max_prefix_warning=dict(required=False, type="bool"),
next_hop_self=dict(required=False, type="bool"),
next_hop_third_party=dict(required=False, type="bool"),
prefix_list_in=dict(required=False, type="str"),
prefix_list_out=dict(required=False, type="str"),
route_map_in=dict(required=False, type="str"),
route_map_out=dict(required=False, type="str"),
route_reflector_client=dict(required=False, type="bool"),
send_community=dict(
required=False,
choices=["none", "both", "extended", "standard", "default"],
),
soft_reconfiguration_in=dict(
required=False, type="str", choices=["enable", "always", "inherit"]
),
soo=dict(required=False, type="str"),
suppress_inactive=dict(required=False, type="bool"),
unsuppress_map=dict(required=False, type="str"),
weight=dict(required=False, type="str"),
state=dict(
choices=["present", "absent"], default="present", required=False
),
rewrite_evpn_rt_asn=dict(required=False, type="bool"),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[
["advertise_map_exist", "advertise_map_non_exist"],
["max_prefix_interval", "max_prefix_warning"],
["default_originate", "default_originate_route_map"],
["allowas_in", "allowas_in_max"],
],
supports_check_mode=True,
)
warnings = list()
result = dict(changed=False, warnings=warnings)
state = module.params["state"]
for key in [
"max_prefix_interval",
"max_prefix_warning",
"max_prefix_threshold",
]:
if module.params[key] and not module.params["max_prefix_limit"]:
module.fail_json(
msg="max_prefix_limit is required when using %s" % key
)
if module.params["vrf"] == "default" and module.params["soo"]:
module.fail_json(msg="SOO is only allowed in non-default VRF")
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args, warnings)
if existing.get("asn") and state == "present":
if existing.get("asn") != module.params["asn"]:
module.fail_json(
msg="Another BGP ASN already exists.",
proposed_asn=module.params["asn"],
existing_asn=existing.get("asn"),
)
for param in ["advertise_map_exist", "advertise_map_non_exist"]:
if module.params[param] == ["default"]:
module.params[param] = "default"
proposed_args = dict(
(k, v) for k, v in module.params.items() if v is not None and k in args
)
proposed = {}
for key, value in proposed_args.items():
if key not in ["asn", "vrf", "neighbor"]:
if not isinstance(value, list):
if str(value).lower() == "true":
value = True
elif str(value).lower() == "false":
value = False
elif str(value).lower() == "default":
if key in BOOL_PARAMS:
value = False
else:
value = "default"
elif key == "send_community" and str(value).lower() == "none":
value = "default"
if existing.get(key) != value:
proposed[key] = value
candidate = CustomNetworkConfig(indent=3)
if state == "present":
state_present(module, existing, proposed, candidate)
elif state == "absent" and existing:
state_absent(module, existing, candidate)
if candidate:
candidate = candidate.items_text()
if not module.check_mode:
responses = load_config(module, candidate)
if responses:
for resp in responses:
if resp:
if resp.endswith("is valid only for EBGP peers"):
module.fail_json(msg=resp)
result["changed"] = True
result["commands"] = candidate
else:
result["commands"] = []
module.exit_json(**result)
if __name__ == "__main__":
main()