File: //lib/python3.9/site-packages/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = """
module: nxos_pim_interface
extends_documentation_fragment:
- cisco.nxos.nxos
short_description: Manages PIM interface configuration.
description:
- Manages PIM interface configuration settings.
version_added: 1.0.0
author:
- Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- Unsupported for Cisco MDS
- When C(state=default), supported params will be reset to a default state. These
include C(dr_prio), C(hello_auth_key), C(hello_interval), C(jp_policy_out), C(jp_policy_in),
C(jp_type_in), C(jp_type_out), C(border), C(neighbor_policy), C(neighbor_type).
- The C(hello_auth_key) param is not idempotent.
- C(hello_auth_key) only supports clear text passwords.
- When C(state=absent), pim interface configuration will be set to defaults and pim-sm
will be disabled on the interface.
- PIM must be enabled on the device to use this module.
- This module is for Layer 3 interfaces.
options:
interface:
description:
- Full name of the interface such as Ethernet1/33.
type: str
required: true
sparse:
description:
- Enable/disable sparse-mode on the interface.
type: bool
default: false
bfd:
description:
- Enables BFD for PIM at the interface level. This overrides the bfd variable
set at the pim global level.
- Valid values are 'enable', 'disable' or 'default'.
- "Dependency: ''feature bfd''"
type: str
choices:
- enable
- disable
- default
dr_prio:
description:
- Configures priority for PIM DR election on interface.
type: str
hello_auth_key:
description:
- Authentication for hellos on this interface.
type: str
hello_interval:
description:
- Hello interval in milliseconds or seconds for this interface.
- Use the option I(hello_interval_ms) to specify if the given value is in
milliseconds or seconds. The default is seconds.
type: int
hello_interval_ms:
description:
- Specifies that the hello_interval is in milliseconds.
- When set to True, this indicates that the user is providing the
hello_interval in milliseconds and hence, no conversion is required.
type: bool
version_added: 2.0.0
jp_policy_out:
description:
- Policy for join-prune messages (outbound).
type: str
jp_policy_in:
description:
- Policy for join-prune messages (inbound).
type: str
jp_type_out:
description:
- Type of policy mapped to C(jp_policy_out).
type: str
choices:
- prefix
- routemap
jp_type_in:
description:
- Type of policy mapped to C(jp_policy_in).
type: str
choices:
- prefix
- routemap
border:
description:
- Configures interface to be a boundary of a PIM domain.
type: bool
default: false
neighbor_policy:
description:
- Configures a neighbor policy for filtering adjacencies.
type: str
neighbor_type:
description:
- Type of policy mapped to neighbor_policy.
type: str
choices:
- prefix
- routemap
state:
description:
- Manages desired state of the resource.
type: str
choices:
- present
- absent
- default
default: present
"""
EXAMPLES = """
- name: Ensure PIM is not running on the interface
cisco.nxos.nxos_pim_interface:
interface: eth1/33
state: absent
- name: Ensure the interface has pim-sm enabled with the appropriate priority and
hello interval
cisco.nxos.nxos_pim_interface:
interface: eth1/33
dr_prio: 10
hello_interval: 40
state: present
- name: Ensure join-prune policies exist
cisco.nxos.nxos_pim_interface:
interface: eth1/33
jp_policy_in: JPIN
jp_policy_out: JPOUT
jp_type_in: routemap
jp_type_out: routemap
- name: disable bfd on the interface
cisco.nxos.nxos_pim_interface:
interface: eth1/33
bfd: disable
- name: Ensure defaults are in place
cisco.nxos.nxos_pim_interface:
interface: eth1/33
state: default
"""
RETURN = r"""
commands:
description: command sent to the device
returned: always
type: list
sample: ["interface eth1/33",
"ip pim neighbor-policy test",
"ip pim bfd-instance disable",
"ip pim neighbor-policy test"
]
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
get_config,
get_interface_type,
load_config,
run_commands,
)
PARAM_TO_COMMAND_KEYMAP = {
"interface": "",
"bfd": "ip pim bfd-instance",
"sparse": "ip pim sparse-mode",
"dr_prio": "ip pim dr-priority {0}",
"hello_interval": "ip pim hello-interval {0}",
"hello_auth_key": "ip pim hello-authentication ah-md5 {0}",
"border": "ip pim border",
"jp_policy_out": "ip pim jp-policy prefix-list {0} out",
"jp_policy_in": "ip pim jp-policy prefix-list {0} in",
"jp_type_in": "",
"jp_type_out": "",
"neighbor_policy": "ip pim neighbor-policy prefix-list {0}",
"neighbor_type": "",
}
PARAM_TO_DEFAULT_KEYMAP = {
"bfd": "default",
"dr_prio": "1",
"hello_interval": "30000",
"sparse": False,
"border": False,
"hello_auth_key": False,
}
BFD_KEYMAP = {
None: None,
"default": "no ip pim bfd-instance",
"disable": "ip pim bfd-instance disable",
"enable": "ip pim bfd-instance",
}
def execute_show_command(command, module, text=False):
if text:
cmds = [{"command": command, "output": "text"}]
else:
cmds = [{"command": command, "output": "json"}]
return run_commands(module, cmds)
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def local_existing(gexisting):
jp_bidir = False
isauth = False
if gexisting:
jp_bidir = gexisting.get("jp_bidir")
isauth = gexisting.get("isauth")
if jp_bidir and isauth:
gexisting.pop("jp_bidir")
gexisting.pop("isauth")
return gexisting, jp_bidir, isauth
def get_interface_mode(interface, intf_type, module):
mode = "unknown"
command = "show interface {0}".format(interface)
body = execute_show_command(command, module)
try:
interface_table = body[0]["TABLE_interface"]["ROW_interface"]
except (KeyError, AttributeError, IndexError):
return mode
if intf_type in ["ethernet", "portchannel"]:
mode = str(interface_table.get("eth_mode", "layer3"))
if mode in ["access", "trunk"]:
mode = "layer2"
elif mode == "routed":
mode = "layer3"
elif intf_type in ["loopback", "svi"]:
mode = "layer3"
return mode
def get_pim_interface(module, interface):
pim_interface = {}
body = get_config(module, flags=["interface {0}".format(interface)])
pim_interface["bfd"] = "default"
pim_interface["neighbor_type"] = None
pim_interface["neighbor_policy"] = None
pim_interface["jp_policy_in"] = None
pim_interface["jp_policy_out"] = None
pim_interface["jp_type_in"] = None
pim_interface["jp_type_out"] = None
pim_interface["jp_bidir"] = False
pim_interface["isauth"] = False
if body:
all_lines = body.splitlines()
for each in all_lines:
if "jp-policy" in each:
policy_name = re.search(
r"ip pim jp-policy(?: prefix-list)? (\S+)(?: \S+)?",
each,
).group(1)
if "prefix-list" in each:
ptype = "prefix"
else:
ptype = "routemap"
if "out" in each:
pim_interface["jp_policy_out"] = policy_name
pim_interface["jp_type_out"] = ptype
elif "in" in each:
pim_interface["jp_policy_in"] = policy_name
pim_interface["jp_type_in"] = ptype
else:
pim_interface["jp_policy_in"] = policy_name
pim_interface["jp_policy_out"] = policy_name
pim_interface["jp_bidir"] = True
elif "neighbor-policy" in each:
pim_interface["neighbor_policy"] = re.search(
r"ip pim neighbor-policy(?: prefix-list)? (\S+)",
each,
).group(1)
if "prefix-list" in each:
pim_interface["neighbor_type"] = "prefix"
else:
pim_interface["neighbor_type"] = "routemap"
elif "ah-md5" in each:
pim_interface["isauth"] = True
elif "sparse-mode" in each:
pim_interface["sparse"] = True
elif "bfd-instance" in each:
m = re.search(r"ip pim bfd-instance(?P<disable> disable)?", each)
if m:
pim_interface["bfd"] = "disable" if m.group("disable") else "enable"
elif "border" in each:
pim_interface["border"] = True
elif "hello-interval" in each:
pim_interface["hello_interval"] = re.search(
r"ip pim hello-interval (\d+)",
body,
).group(1)
elif "dr-priority" in each:
pim_interface["dr_prio"] = re.search(r"ip pim dr-priority (\d+)", body).group(1)
return pim_interface
def fix_delta(delta, existing):
for key in list(delta):
if key in ["dr_prio", "hello_interval", "sparse", "border"]:
if delta.get(key) == PARAM_TO_DEFAULT_KEYMAP.get(key) and existing.get(key) is None:
delta.pop(key)
return delta
def config_pim_interface(delta, existing, jp_bidir, isauth):
command = None
commands = []
delta = fix_delta(delta, existing)
if jp_bidir:
if delta.get("jp_policy_in") or delta.get("jp_policy_out"):
if existing.get("jp_type_in") == "prefix":
command = "no ip pim jp-policy prefix-list {0}".format(existing.get("jp_policy_in"))
else:
command = "no ip pim jp-policy {0}".format(existing.get("jp_policy_in"))
if command:
commands.append(command)
for k, v in delta.items():
if k in [
"bfd",
"dr_prio",
"hello_interval",
"hello_auth_key",
"border",
"sparse",
]:
if k == "bfd":
command = BFD_KEYMAP[v]
elif v:
command = PARAM_TO_COMMAND_KEYMAP.get(k).format(v)
elif k == "hello_auth_key":
if isauth:
command = "no ip pim hello-authentication ah-md5"
else:
command = "no " + PARAM_TO_COMMAND_KEYMAP.get(k).format(v)
if command:
commands.append(command)
elif k in [
"neighbor_policy",
"jp_policy_in",
"jp_policy_out",
"neighbor_type",
]:
if k in ["neighbor_policy", "neighbor_type"]:
temp = delta.get("neighbor_policy") or existing.get("neighbor_policy")
if delta.get("neighbor_type") == "prefix":
command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
elif delta.get("neighbor_type") == "routemap":
command = "ip pim neighbor-policy {0}".format(temp)
elif existing.get("neighbor_type") == "prefix":
command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
elif existing.get("neighbor_type") == "routemap":
command = "ip pim neighbor-policy {0}".format(temp)
elif k in ["jp_policy_in", "jp_type_in"]:
temp = delta.get("jp_policy_in") or existing.get("jp_policy_in")
if delta.get("jp_type_in") == "prefix":
command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
elif delta.get("jp_type_in") == "routemap":
command = "ip pim jp-policy {0} in".format(temp)
elif existing.get("jp_type_in") == "prefix":
command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
elif existing.get("jp_type_in") == "routemap":
command = "ip pim jp-policy {0} in".format(temp)
elif k in ["jp_policy_out", "jp_type_out"]:
temp = delta.get("jp_policy_out") or existing.get("jp_policy_out")
if delta.get("jp_type_out") == "prefix":
command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
elif delta.get("jp_type_out") == "routemap":
command = "ip pim jp-policy {0} out".format(temp)
elif existing.get("jp_type_out") == "prefix":
command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
elif existing.get("jp_type_out") == "routemap":
command = "ip pim jp-policy {0} out".format(temp)
if command:
commands.append(command)
command = None
if "no ip pim sparse-mode" in commands:
# sparse is long-running on some platforms, process it last
commands.remove("no ip pim sparse-mode")
commands.append("no ip pim sparse-mode")
return commands
def get_pim_interface_defaults():
args = dict(
dr_prio=PARAM_TO_DEFAULT_KEYMAP.get("dr_prio"),
bfd=PARAM_TO_DEFAULT_KEYMAP.get("bfd"),
border=PARAM_TO_DEFAULT_KEYMAP.get("border"),
sparse=PARAM_TO_DEFAULT_KEYMAP.get("sparse"),
hello_interval=PARAM_TO_DEFAULT_KEYMAP.get("hello_interval"),
hello_auth_key=PARAM_TO_DEFAULT_KEYMAP.get("hello_auth_key"),
)
default = dict((param, value) for (param, value) in args.items() if value is not None)
return default
def default_pim_interface_policies(existing, jp_bidir):
commands = []
if jp_bidir:
if existing.get("jp_policy_in") or existing.get("jp_policy_out"):
if existing.get("jp_type_in") == "prefix":
command = "no ip pim jp-policy prefix-list {0}".format(existing.get("jp_policy_in"))
if command:
commands.append(command)
elif not jp_bidir:
command = None
for k in existing:
if k == "jp_policy_in":
if existing.get("jp_policy_in"):
if existing.get("jp_type_in") == "prefix":
command = "no ip pim jp-policy prefix-list {0} in".format(
existing.get("jp_policy_in"),
)
else:
command = "no ip pim jp-policy {0} in".format(existing.get("jp_policy_in"))
elif k == "jp_policy_out":
if existing.get("jp_policy_out"):
if existing.get("jp_type_out") == "prefix":
command = "no ip pim jp-policy prefix-list {0} out".format(
existing.get("jp_policy_out"),
)
else:
command = "no ip pim jp-policy {0} out".format(
existing.get("jp_policy_out"),
)
if command:
commands.append(command)
command = None
if existing.get("neighbor_policy"):
command = "no ip pim neighbor-policy"
commands.append(command)
return commands
def config_pim_interface_defaults(existing, jp_bidir, isauth):
command = []
# returns a dict
defaults = get_pim_interface_defaults()
delta = dict(set(defaults.items()).difference(existing.items()))
if delta:
# returns a list
command = config_pim_interface(delta, existing, jp_bidir, isauth)
comm = default_pim_interface_policies(existing, jp_bidir)
if comm:
for each in comm:
command.append(each)
return command
def normalize_proposed_values(proposed, module):
keys = proposed.keys()
if "bfd" in keys:
# bfd is a tri-state string: enable, disable, default
proposed["bfd"] = proposed["bfd"].lower()
if "hello_interval" in keys:
hello_interval = proposed["hello_interval"]
if not module.params["hello_interval_ms"]:
hello_interval = hello_interval * 1000
proposed["hello_interval"] = str(hello_interval)
def main():
argument_spec = dict(
interface=dict(type="str", required=True),
sparse=dict(type="bool", default=False),
dr_prio=dict(type="str"),
hello_auth_key=dict(type="str", no_log=True),
hello_interval=dict(type="int"),
hello_interval_ms=dict(type="bool"),
jp_policy_out=dict(type="str"),
jp_policy_in=dict(type="str"),
jp_type_out=dict(type="str", choices=["prefix", "routemap"]),
jp_type_in=dict(type="str", choices=["prefix", "routemap"]),
bfd=dict(type="str", choices=["enable", "disable", "default"]),
border=dict(type="bool", default=False),
neighbor_policy=dict(type="str"),
neighbor_type=dict(type="str", choices=["prefix", "routemap"]),
state=dict(
type="str",
default="present",
choices=["absent", "default", "present"],
),
)
required_by = {"hello_interval_ms": "hello_interval"}
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_by=required_by,
)
warnings = list()
results = {"changed": False, "commands": [], "warnings": warnings}
state = module.params["state"]
interface = module.params["interface"]
jp_type_in = module.params["jp_type_in"]
jp_type_out = module.params["jp_type_out"]
jp_policy_in = module.params["jp_policy_in"]
jp_policy_out = module.params["jp_policy_out"]
neighbor_policy = module.params["neighbor_policy"]
neighbor_type = module.params["neighbor_type"]
intf_type = get_interface_type(interface)
if get_interface_mode(interface, intf_type, module) == "layer2":
module.fail_json(msg="this module only works on Layer 3 interfaces.")
if jp_policy_in:
if not jp_type_in:
module.fail_json(msg="jp_type_in required when using jp_policy_in.")
if jp_policy_out:
if not jp_type_out:
module.fail_json(msg="jp_type_out required when using jp_policy_out.")
if neighbor_policy:
if not neighbor_type:
module.fail_json(msg="neighbor_type required when using neighbor_policy.")
get_existing = get_pim_interface(module, interface)
existing, jp_bidir, isauth = local_existing(get_existing)
args = PARAM_TO_COMMAND_KEYMAP.keys()
proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
normalize_proposed_values(proposed, module)
delta = dict(set(proposed.items()).difference(existing.items()))
commands = []
if state == "present":
if delta:
command = config_pim_interface(delta, existing, jp_bidir, isauth)
if command:
commands.append(command)
elif state == "default" or state == "absent":
defaults = config_pim_interface_defaults(existing, jp_bidir, isauth)
if defaults:
commands.append(defaults)
if commands:
commands.insert(0, ["interface {0}".format(interface)])
cmds = flatten_list(commands)
if cmds:
results["changed"] = True
if not module.check_mode:
load_config(module, cmds)
if "configure" in cmds:
cmds.pop(0)
results["commands"] = cmds
module.exit_json(**results)
if __name__ == "__main__":
main()