Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enables pure IPv6 configurations for nd_setup module on ND version 3.0.1 and later (DCNE-241) #108

Merged
merged 4 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions plugins/module_utils/nd_argument_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def ntp_keys_spec():

def network_spec(vlan=False):
spec = dict(
ipv4_address=dict(type="str", aliases=["ip"], required=True),
ipv4_gateway=dict(type="str", aliases=["gateway"], required=True),
ipv4_address=dict(type="str", aliases=["ip"]),
ipv4_gateway=dict(type="str", aliases=["gateway"]),
ipv6_address=dict(type="str"),
ipv6_gateway=dict(type="str"),
)
Expand Down
68 changes: 57 additions & 11 deletions plugins/modules/nd_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@
description:
- The management IP address of the node.
type: str
required: true
username:
description:
- The username of the node.
Expand All @@ -185,13 +184,11 @@
description:
- The IPv4 address of the management network.
type: str
required: true
aliases: [ ip ]
ipv4_gateway:
description:
- The IPv4 gateway of the management network.
type: str
required: true
aliases: [ gateway ]
ipv6_address:
description:
Expand All @@ -211,13 +208,11 @@
description:
- The IPv4 address of the data network.
type: str
required: true
aliases: [ ip ]
ipv4_gateway:
description:
- The IPv4 gateway of the data network.
type: str
required: true
aliases: [ gateway ]
ipv6_address:
description:
Expand Down Expand Up @@ -270,7 +265,7 @@
- This option is only applicable for ND version 3.1.1 and later.
type: list
elements: str
choices: [ ndo, ndfc, ndi ]
choices: [ ndo, ndfc, ndi-virtual, ndi-physical ]
aliases: [ mode ]
external_services:
description:
Expand Down Expand Up @@ -357,11 +352,56 @@
from ansible_collections.cisco.nd.plugins.module_utils.constants import ND_SETUP_NODE_ROLE_MAPPING


def check_network_requirements(nd, version, nodes, internal_network_ipv4, internal_network_ipv6):
if version >= "3.0.1":
# checking minimum requirements for internal network
if not all(internal_network_ipv4) and not all(internal_network_ipv6):
nd.fail_json(msg="Application and service network addresses, IPv4 or IPv6, are required during ND setup.")
# Conditions fo Dual Stack configuration for internal network
gmicol marked this conversation as resolved.
Show resolved Hide resolved
elif all(internal_network_ipv4) and any(internal_network_ipv6) and not all(internal_network_ipv6):
nd.fail_json(
msg="For a dual stack configuration, application and service network IPv6 addresses are required. Otherwise, the extra one must be removed."
gmicol marked this conversation as resolved.
Show resolved Hide resolved
)
elif all(internal_network_ipv6) and any(internal_network_ipv4) and not all(internal_network_ipv4):
nd.fail_json(
msg="For a dual stack configuration, application and service network IPv4 addresses are required. Otherwise, the extra one must be removed."
gmicol marked this conversation as resolved.
Show resolved Hide resolved
)
for node in nodes:
for network in ["data_network", "management_network"]:
network_ipv4_config = [node[network].get(ip) for ip in ["ipv4_address", "ipv4_gateway"]]
network_ipv6_config = [node[network].get(ip) for ip in ["ipv6_address", "ipv6_gateway"]]
# checking minimum requirements for external network
if not all(network_ipv4_config) and not all(network_ipv6_config):
nd.fail_json(msg="A complete IPv4 subnet/gateway or IPv6 subnet/gateway configuration is required in node's {0}.".format(network))
# Conditions fo Dual Stack configuration for external network
gmicol marked this conversation as resolved.
Show resolved Hide resolved
elif all(network_ipv4_config) and any(network_ipv6_config) and not all(network_ipv6_config):
nd.fail_json(
msg="For a dual stack configuration,"
/ " a complete IPv6 subnet/gateway configuration in node's {0} must be provided.".format(network)
/ " Otherwise, the extra one must be removed"
gmicol marked this conversation as resolved.
Show resolved Hide resolved
)
elif all(network_ipv6_config) and any(network_ipv4_config) and not all(network_ipv4_config):
nd.fail_json(
msg="For a dual stack configuration,"
/ " a complete IPv4 subnet/gateway configuration in node's {0} must be provided.".format(network)
/ " Otherwise, the extra one must be removed"
gmicol marked this conversation as resolved.
Show resolved Hide resolved
)
else:
# checking minimum requirements for internal network
if not all(internal_network_ipv4):
nd.fail_json(msg="Application and service network IPv4 addresses are required during ND setup.")
# checking minimum requirements for external network
for node in nodes:
for network in ["data_network", "management_network"]:
if not all(node[network].get(ip) for ip in ["ipv4_address", "ipv4_gateway"]):
nd.fail_json(msg="A complete IPv4 subnet/gateway configuration is required in node's {0}.".format(network))


def main():
argument_spec = nd_argument_spec()
argument_spec.update(
cluster_name=dict(type="str"),
deployment_mode=dict(type="list", elements="str", choices=["ndo", "ndfc", "ndi"], aliases=["mode"]),
deployment_mode=dict(type="list", elements="str", choices=["ndo", "ndfc", "ndi-virtual", "ndi-physical"], aliases=["mode"]),
external_services=dict(
type="dict",
options=dict(
Expand Down Expand Up @@ -394,7 +434,7 @@ def main():
hostname=dict(type="str", required=True),
serial_number=dict(type="str", required=True),
role=dict(type="str", default="primary", choices=["primary", "secondary", "standby"], aliases=["type"]),
management_ip_address=dict(type="str", required=True),
management_ip_address=dict(type="str"),
username=dict(type="str", required=True),
password=dict(type="str", required=True, no_log=True),
management_network=dict(type="dict", required=True, options=network_spec()),
Expand All @@ -409,7 +449,7 @@ def main():
argument_spec=argument_spec,
supports_check_mode=True,
required_if=[
["state", "present", ["cluster_name", "dns_server", "app_network", "service_network", "nodes"]],
["state", "present", ["cluster_name", "dns_server", "nodes"]],
],
required_together=[
["proxy_username", "proxy_password"],
Expand Down Expand Up @@ -442,6 +482,12 @@ def main():
if state == "query":
nd.existing = nd.request("/clusterstatus/install", method="GET")
else:
nd_version = nd.query_obj("/version.json")
nd_version = ".".join(str(nd_version[key]) for key in ["major", "minor", "maintenance"])
# Checking internal and external network requirements
check_network_requirements(nd, nd_version, nodes, (app_network, service_network), (app_network_ipv6, service_network_ipv6))

# Checking cluster name validation
if len(cluster_name) > 63:
nd.fail_json("A length of 1 to 63 characters is allowed.")
elif len(re.findall(r"[^a-zA-Z0-9-]", cluster_name)) > 0:
Expand Down Expand Up @@ -520,9 +566,9 @@ def main():
}

# Deployment mode options introduced in ND version 3.1.1
if isinstance(deployment_mode, list):
if isinstance(deployment_mode, list) and nd_version >= "3.1.1":
payload["clusterConfig"]["deploymentMode"] = deployment_mode if len(deployment_mode) > 1 else deployment_mode[0]
if external_services is not None and any(service in {"ndi", "ndfc"} for service in deployment_mode):
if external_services is not None and any(service in {"ndi-virtual", "ndi-physical", "ndfc"} for service in deployment_mode):
payload["clusterConfig"]["externalServices"] = []
if external_services.get("management_service_ips") is not None:
payload["clusterConfig"]["externalServices"].append(
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/targets/nd_setup/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,24 @@
cluster_name: clusterone-
ignore_errors: true
register: cluster_name_invalid_hyphen

- name: Install ND without management network ipv4 address
cisco.nd.nd_setup:
<<: *nd_setup_test_config
nodes:
- hostname: Test
serial_number: "{{ serial_number }}"
role: primary
management_ip_address: "{{ management_ip_address }}"
username: "{{ deployment_username | default('rescue-user') }}"
password: "{{ deployment_password }}"
management_network:
ipv4_gateway: "{{ management_gateway }}"
gmicol marked this conversation as resolved.
Show resolved Hide resolved
data_network:
ipv4_address: "{{ data_ip }}"
ipv4_gateway: "{{ data_gateway }}"
ignore_errors: true
register: cluster_invalid_node_management_network

- name: Install ND in normal mode
cisco.nd.nd_setup:
Expand All @@ -290,6 +308,7 @@
- cluster_name_length_error.msg == "A length of 1 to 63 characters is allowed."
- cluster_name_invalid_chars.msg == "Valid characters include letters, digits and hyphen."
- cluster_name_invalid_hyphen.msg == "The name cannot start or end with a hyphen."
- cluster_invalid_node_management_network.msg == "Missing IPv4 subnet/gateway or IPv6 subnet/gateway in node management network configuration."
- cluster_cm.current.clusterConfig.appNetwork == "{{ app_network }}"
- cluster_cm.current.clusterConfig.nameServers.0 == "{{ dns_server }}"
- cluster_cm.current.clusterConfig.ntpConfig.servers.0.host == "{{ ntp_server }}"
Expand Down
Loading