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_interface_ospf
extends_documentation_fragment:
- cisco.nxos.nxos
short_description: (deprecated, removed after 2022-10-26) Manages configuration of an OSPF interface instance.
description:
- Manages configuration of an OSPF interface instance.
version_added: 1.0.0
deprecated:
alternative: nxos_ospf_interfaces
why: Updated modules released with more functionality
removed_at_date: '2022-10-26'
author: Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- Unsupported for Cisco MDS
- Default, where supported, restores params default value.
- To remove an existing authentication configuration you should use C(message_digest_key_id=default)
plus all other options matching their existing values.
- Loopback interfaces only support ospf network type 'point-to-point'.
- C(state=absent) removes the whole OSPF interface configuration.
options:
interface:
description:
- Name of this cisco_interface resource. Valid value is a string.
required: true
type: str
ospf:
description:
- Name of the ospf instance.
required: true
type: str
area:
description:
- Ospf area associated with this cisco_interface_ospf instance. Valid values are
a string, formatted as an IP address (i.e. "0.0.0.0") or as an integer.
required: true
type: str
bfd:
description:
- Enables bfd at interface level. This overrides the bfd variable set at the ospf
router level.
- Valid values are 'enable', 'disable' or 'default'.
- "Dependency: ''feature bfd''"
type: str
choices:
- enable
- disable
- default
cost:
description:
- The cost associated with this cisco_interface_ospf instance.
type: str
hello_interval:
description:
- Time between sending successive hello packets. Valid values are an integer or
the keyword 'default'.
type: str
dead_interval:
description:
- Time interval an ospf neighbor waits for a hello packet before tearing down
adjacencies. Valid values are an integer or the keyword 'default'.
type: str
passive_interface:
description:
- Enable or disable passive-interface state on this interface. true - (enable)
Prevent OSPF from establishing an adjacency or sending routing updates on this
interface. false - (disable) Override global 'passive-interface default' for
this interface.
type: bool
network:
description:
- Specifies interface ospf network type. Valid values are 'point-to-point' or
'broadcast'.
choices:
- point-to-point
- broadcast
type: str
message_digest:
description:
- Enables or disables the usage of message digest authentication.
type: bool
message_digest_key_id:
description:
- Md5 authentication key-id associated with the ospf instance. If this is present,
message_digest_encryption_type, message_digest_algorithm_type and message_digest_password
are mandatory. Valid value is an integer and 'default'.
type: str
message_digest_algorithm_type:
description:
- Algorithm used for authentication among neighboring routers within an area.
Valid values are 'md5' and 'default'.
choices:
- md5
- default
type: str
message_digest_encryption_type:
description:
- Specifies the scheme used for encrypting message_digest_password. Valid values
are '3des' or 'cisco_type_7' encryption or 'default'.
choices:
- cisco_type_7
- 3des
- default
type: str
message_digest_password:
description:
- Specifies the message_digest password. Valid value is a string.
type: str
state:
description:
- Determines whether the config should be present or not on the device.
default: present
choices:
- present
- absent
type: str
"""
EXAMPLES = """
- cisco.nxos.nxos_interface_ospf:
interface: ethernet1/32
ospf: 1
area: 1
bfd: disable
cost: default
- cisco.nxos.nxos_interface_ospf:
interface: loopback0
ospf: prod
area: 0.0.0.0
bfd: enable
network: point-to-point
state: present
"""
RETURN = """
commands:
description: commands sent to the device
returned: always
type: list
sample: ["interface Ethernet1/32", "ip router ospf 1 area 0.0.0.1", "ip ospf bfd disable"]
"""
import re
import struct
import socket
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 = ["passive_interface", "message_digest"]
PARAM_TO_COMMAND_KEYMAP = {
"interface": "",
"cost": "ip ospf cost",
"ospf": "ip router ospf",
"area": "ip router ospf",
"bfd": "ip ospf bfd",
"hello_interval": "ip ospf hello-interval",
"dead_interval": "ip ospf dead-interval",
"passive_interface": "ip ospf passive-interface",
"message_digest": "ip ospf authentication message-digest",
"message_digest_key_id": "ip ospf message-digest-key",
"message_digest_algorithm_type": "ip ospf message-digest-key",
"message_digest_encryption_type": "ip ospf message-digest-key",
"message_digest_password": "ip ospf message-digest-key",
"network": "ip ospf network",
}
def get_value(arg, config, module):
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
)
if command == "ip router ospf":
value = ""
if has_command_val:
value_list = has_command_val.group("value").split()
if arg == "ospf":
value = value_list[0]
elif arg == "area":
value = value_list[2]
value = normalize_area(value, module)
elif command == "ip ospf message-digest-key":
value = ""
if has_command_val:
value_list = has_command_val.group("value").split()
if arg == "message_digest_key_id":
value = value_list[0]
elif arg == "message_digest_algorithm_type":
value = value_list[1]
elif arg == "message_digest_encryption_type":
value = value_list[2]
if value == "3":
value = "3des"
elif value == "7":
value = "cisco_type_7"
elif arg == "message_digest_password":
value = value_list[3]
elif arg == "passive_interface":
has_no_command = re.search(
r"\s+no\s+{0}\s*$".format(command), config, re.M
)
if has_no_command:
value = False
elif has_command:
value = True
else:
value = None
elif arg == "bfd":
m = re.search(r"\s*ip ospf bfd(?P<disable> disable)?", config)
if m:
value = "disable" if m.group("disable") else "enable"
else:
value = "default"
elif arg in BOOL_PARAMS:
value = bool(has_command)
else:
value = ""
if has_command_val:
value = has_command_val.group("value")
return value
def get_existing(module, args):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
if module.params["interface"].startswith("loopback") or module.params[
"interface"
].startswith("port-channel"):
parents = ["interface {0}".format(module.params["interface"])]
else:
parents = [
"interface {0}".format(module.params["interface"].capitalize())
]
config = netcfg.get_section(parents)
if "ospf" in config:
for arg in args:
if arg not in ["interface"]:
existing[arg] = get_value(arg, config, module)
existing["interface"] = module.params["interface"]
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key, value in table.items():
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = value
return new_dict
def get_default_commands(existing, proposed, existing_commands, key, module):
commands = list()
existing_value = existing_commands.get(key)
if key.startswith("ip ospf message-digest-key"):
check = False
for param in [
"message_digest_encryption_type",
"message_digest_algorithm_type",
"message_digest_password",
]:
if existing[param] == proposed[param]:
check = True
if check:
if existing["message_digest_encryption_type"] == "3des":
encryption_type = "3"
elif existing["message_digest_encryption_type"] == "cisco_type_7":
encryption_type = "7"
command = "no {0} {1} {2} {3} {4}".format(
key,
existing["message_digest_key_id"],
existing["message_digest_algorithm_type"],
encryption_type,
existing["message_digest_password"],
)
commands.append(command)
elif "ip ospf bfd" in key:
commands.append("no {0}".format(key))
elif "passive-interface" in key:
commands.append("default ip ospf passive-interface")
else:
commands.append("no {0} {1}".format(key, existing_value))
return commands
def get_custom_command(existing_cmd, proposed, key, module):
commands = list()
if key == "ip router ospf":
command = "{0} {1} area {2}".format(
key, proposed["ospf"], proposed["area"]
)
if command not in existing_cmd:
commands.append(command)
if key == "ip ospf network":
command = "{0} {1}".format(key, proposed["network"])
if command not in existing_cmd:
commands.append(command)
elif key.startswith("ip ospf message-digest-key"):
if (
proposed["message_digest_key_id"] != "default"
and "options" not in key
):
if proposed["message_digest_encryption_type"] == "3des":
encryption_type = "3"
elif proposed["message_digest_encryption_type"] == "cisco_type_7":
encryption_type = "7"
command = "{0} {1} {2} {3} {4}".format(
key,
proposed["message_digest_key_id"],
proposed["message_digest_algorithm_type"],
encryption_type,
proposed["message_digest_password"],
)
commands.append(command)
return commands
def state_present(module, existing, proposed, candidate):
commands = list()
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 existing_commands.get(key):
if key == "ip router ospf":
if proposed["area"] == existing["area"]:
continue
if existing_commands[key] == value:
continue
if key == "ip ospf passive-interface" and module.params.get(
"interface"
).upper().startswith("LO"):
module.fail_json(
msg="loopback interface does not support passive_interface"
)
if (
key == "ip ospf network"
and value == "broadcast"
and module.params.get("interface").upper().startswith("LO")
):
module.fail_json(
msg="loopback interface does not support ospf network type broadcast"
)
if key == "ip ospf bfd":
cmd = key
if "disable" in value:
cmd += " disable"
elif "default" in value and existing.get("bfd") is not None:
cmd = "no " + cmd
commands.append(cmd)
continue
if value is True:
commands.append(key)
elif value is False:
commands.append("no {0}".format(key))
elif value == "default":
if existing_commands.get(key):
commands.extend(
get_default_commands(
existing, proposed, existing_commands, key, module
)
)
else:
if key == "ip router ospf" or key.startswith(
"ip ospf message-digest-key"
):
commands.extend(
get_custom_command(commands, proposed, key, module)
)
else:
command = "{0} {1}".format(key, value.lower())
commands.append(command)
if commands:
parents = [
"interface {0}".format(module.params["interface"].capitalize())
]
candidate.add(commands, parents=parents)
def state_absent(module, existing, proposed, candidate):
commands = []
parents = ["interface {0}".format(module.params["interface"].capitalize())]
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in existing_commands.items():
if "ip ospf bfd" in key:
if "default" not in value:
# cli is present when enabled or disabled; this removes either case
commands.append("no ip ospf bfd")
continue
if "ip ospf passive-interface" in key and value is not None:
# cli is present for both enabled or disabled; 'no' will not remove
commands.append("default ip ospf passive-interface")
continue
if value:
if key.startswith("ip ospf message-digest-key"):
if "options" not in key:
if existing["message_digest_encryption_type"] == "3des":
encryption_type = "3"
elif (
existing["message_digest_encryption_type"]
== "cisco_type_7"
):
encryption_type = "7"
command = "no {0} {1} {2} {3} {4}".format(
key,
existing["message_digest_key_id"],
existing["message_digest_algorithm_type"],
encryption_type,
existing["message_digest_password"],
)
commands.append(command)
elif key in [
"ip ospf authentication message-digest",
"ip ospf network",
]:
if value:
commands.append("no {0}".format(key))
elif key == "ip router ospf":
command = "no {0} {1} area {2}".format(
key, proposed["ospf"], proposed["area"]
)
if command not in commands:
commands.append(command)
else:
existing_value = existing_commands.get(key)
commands.append("no {0} {1}".format(key, existing_value))
candidate.add(commands, parents=parents)
def normalize_area(area, module):
try:
area = int(area)
area = socket.inet_ntoa(struct.pack("!L", area))
except ValueError:
splitted_area = area.split(".")
if len(splitted_area) != 4:
module.fail_json(msg="Incorrect Area ID format", area=area)
return area
def main():
argument_spec = dict(
interface=dict(required=True, type="str"),
ospf=dict(required=True, type="str"),
area=dict(required=True, type="str"),
bfd=dict(
choices=["enable", "disable", "default"],
required=False,
type="str",
),
cost=dict(required=False, type="str"),
hello_interval=dict(required=False, type="str"),
dead_interval=dict(required=False, type="str"),
passive_interface=dict(required=False, type="bool"),
network=dict(
required=False, type="str", choices=["broadcast", "point-to-point"]
),
message_digest=dict(required=False, type="bool"),
message_digest_key_id=dict(required=False, type="str"),
message_digest_algorithm_type=dict(
required=False, type="str", choices=["md5", "default"]
),
message_digest_encryption_type=dict(
required=False,
type="str",
choices=["cisco_type_7", "3des", "default"],
),
message_digest_password=dict(required=False, type="str", no_log=True),
state=dict(
choices=["present", "absent"], default="present", required=False
),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(
argument_spec=argument_spec,
required_together=[
[
"message_digest_key_id",
"message_digest_algorithm_type",
"message_digest_encryption_type",
"message_digest_password",
]
],
supports_check_mode=True,
)
# Normalize interface input data.
#
# * For port-channel and loopback interfaces expection is all lower case names.
# * All other interfaces the expectation is an uppercase leading character
# followed by lower case characters.
#
if re.match(r"(port-channel|loopback)", module.params["interface"], re.I):
module.params["interface"] = module.params["interface"].lower()
else:
module.params["interface"] = module.params["interface"].capitalize()
warnings = list()
result = {"changed": False, "commands": [], "warnings": warnings}
for param in [
"message_digest_encryption_type",
"message_digest_algorithm_type",
"message_digest_password",
]:
if (
module.params[param] == "default"
and module.params["message_digest_key_id"] != "default"
):
module.exit_json(
msg="Use message_digest_key_id=default to remove an existing authentication configuration"
)
state = module.params["state"]
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args)
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 != "interface":
if str(value).lower() == "true":
value = True
elif str(value).lower() == "false":
value = False
elif str(value).lower() == "default":
value = "default"
elif key == "bfd":
value = str(value).lower()
if existing.get(key) or (not existing.get(key) and value):
proposed[key] = value
elif (
"passive_interface" in key
and existing.get(key) is None
and value is False
):
proposed[key] = value
proposed["area"] = normalize_area(proposed["area"], module)
if "hello_interval" in proposed and proposed["hello_interval"] == "10":
proposed["hello_interval"] = "default"
candidate = CustomNetworkConfig(indent=3)
if state == "present":
state_present(module, existing, proposed, candidate)
elif (
state == "absent"
and existing.get("ospf") == proposed["ospf"]
and existing.get("area") == proposed["area"]
):
state_absent(module, existing, proposed, candidate)
if candidate:
candidate = candidate.items_text()
if not module.check_mode:
load_config(module, candidate)
result["changed"] = True
result["commands"] = candidate
module.exit_json(**result)
if __name__ == "__main__":
main()