Skip to content

Commit

Permalink
Merge branch 'main' into accesspoint_feature_enhancement_ut
Browse files Browse the repository at this point in the history
  • Loading branch information
md-rafeek authored Dec 5, 2024
2 parents 9d5e944 + d7e4ee4 commit 4f0286d
Show file tree
Hide file tree
Showing 12 changed files with 405 additions and 276 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/sanity_tests_devel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,17 @@ jobs:
lintable_files=""
for file in $changed_files; do
# Check if the file belongs to the plugins/modules or playbooks directory
if [[ $file == plugins/modules/* || $file == playbooks/* ]]; then
if [[ $file == playbooks/* ]]; then
lintable_files="$lintable_files $file"
fi
done
echo "Lintable Files: $lintable_files"
if [ -n "$lintable_files" ]; then
echo "Running yamllint test for files: $lintable_files"
yamllint -c .yamllint.yml $lintable_files
echo "Yamllint tests completed successfully."
else
echo "No relevant files to lint."
fi
Expand Down
7 changes: 6 additions & 1 deletion .yamllint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ extends: default
ignore:
- tests
- changelogs/changelog.yaml
# - playbooks
- plugins

level: warning

rules:
line-length:
max: 160
level: warning

indentation:
level: warning

trailing-spaces:
level: warning

new-line-at-end-of-file:
level: warning

key-duplicates:
level: warning
13 changes: 13 additions & 0 deletions changelogs/changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1106,3 +1106,16 @@ releases:
- Changes in sda_extranet_policy_workflow_manager
- Changes in site_workflow_manager
- Modifications due to documentation errors
6.25.1:
release_date: "2024-12-05"
changes:
release_summary: application of unapplied changes and Alias implementation
minor_changes:
- application of the changes made in pull request 207
- Bug fixes in accesspoint_workflow_manager module
- Changes in sda_fabric_devices_workflow_manager module
- Bug fixes in [sda_fabric_sites_zones_workflow_manager module
- Enhancements in [sda_fabric_virtual_networks_workflow_manager module to support batch operation.
- Changes in site_workflow_manager module
- Changes in template_workflow_manager
- Unit test modules added for pnp_workflow_manager module
2 changes: 1 addition & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
namespace: cisco
name: dnac
version: 6.25.0
version: 6.25.1
readme: README.md
authors:
- Rafael Campos <[email protected]>
Expand Down
1 change: 0 additions & 1 deletion playbooks/sda_extranet_policies_workflow_manager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
provider_virtual_network: "VN_1"
subscriber_virtual_networks: ["VN_2", "VN_4"]


- name: Update existing Extranet Policy with Fabric Site(s) specified
cisco.dnac.network_compliance_workflow_manager:
<<: *dnac_login
Expand Down
20 changes: 20 additions & 0 deletions plugins/module_utils/dnac.py
Original file line number Diff line number Diff line change
Expand Up @@ -2161,12 +2161,32 @@ def fn_comp_key(k, dict1, dict2):
return dnac_compare_equality(dict1.get(k), dict2.get(k))


def normalize_ipv6_address(ipv6):
"""
Normalize an IPv6 address for consistent comparison.
"""
if not isinstance(ipv6, str):
raise TypeError("Input must be a string representing an IPv6 address.")

try:
normalized_address = str(ipaddress.IPv6Address(ipv6))
return normalized_address
except ValueError:
# self.log("Invalid IPv6 address: {}".format(ipv6))
return ipv6 # Return as-is if it's not a valid IPv6 address


def dnac_compare_equality(current_value, requested_value):
# print("dnac_compare_equality", current_value, requested_value)
if requested_value is None:
return True
if current_value is None:
return True
if isinstance(current_value, str) and isinstance(requested_value, str):
if ":" in current_value and ":" in requested_value: # Possible IPv6 addresses
current_value = normalize_ipv6_address(current_value)
requested_value = normalize_ipv6_address(requested_value)
return current_value == requested_value
if isinstance(current_value, dict) and isinstance(requested_value, dict):
all_dict_params = list(current_value.keys()) + list(requested_value.keys())
return not any((not fn_comp_key(param, current_value, requested_value) for param in all_dict_params))
Expand Down
169 changes: 106 additions & 63 deletions plugins/modules/network_settings_workflow_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Copyright (c) 2024, Cisco Systems
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)


"""Ansible module to perform operations on global pool, reserve pool and network in Cisco Catalyst Center."""
from __future__ import absolute_import, division, print_function

Expand All @@ -14,38 +15,38 @@
module: network_settings_workflow_manager
short_description: Resource module for IP Address pools and network functions
description:
- Manage operations on Global Pool, Reserve Pool, Network resources.
- API to create/update/delete global pool.
- API to reserve/update/delete an ip subpool from the global pool.
- API to update network settings for DHCP, Syslog, SNMP, NTP, Network AAA, Client and Endpoint AAA,
- Manage operations on Global Pool, Reserve Pool, Network resources.
- API to create/update/delete global pool.
- API to reserve/update/delete an ip subpool from the global pool.
- API to update network settings for DHCP, Syslog, SNMP, NTP, Network AAA, Client and Endpoint AAA,
and/or DNS center server settings.
version_added: '6.6.0'
extends_documentation_fragment:
- cisco.dnac.workflow_manager_params
- cisco.dnac.workflow_manager_params
author: Muthu Rakesh (@MUTHU-RAKESH-27)
Madhan Sankaranarayanan (@madhansansel)
Megha Kandari (@kandarimegha)
options:
config_verify:
description: Set to True to verify the Cisco Catalyst Center after applying the playbook config.
type: bool
default: False
state:
description: The state of Cisco Catalyst Center after module completion.
type: str
choices: [ merged, deleted ]
default: merged
config:
description:
config_verify:
description: Set to True to verify the Cisco Catalyst Center after applying the playbook config.
type: bool
default: False
state:
description: The state of Cisco Catalyst Center after module completion.
type: str
choices: [merged, deleted]
default: merged
config:
description:
- List of details of global pool, reserved pool, network being managed.
type: list
elements: dict
required: true
suboptions:
global_pool_details:
description: Manages IPv4 and IPv6 IP pools in the global level.
type: dict
suboptions:
type: list
elements: dict
required: true
suboptions:
global_pool_details:
description: Manages IPv4 and IPv6 IP pools in the global level.
type: dict
suboptions:
settings:
description: Global Pool's settings.
type: dict
Expand All @@ -57,10 +58,10 @@
suboptions:
name:
description:
- Specifies the name assigned to the Global IP Pool.
- Required for the operations in the Global IP Pool.
- Length should be less than or equal to 100.
- Only letters, numbers and -_./ characters are allowed.
- Specifies the name assigned to the Global IP Pool.
- Required for the operations in the Global IP Pool.
- Length should be less than or equal to 100.
- Only letters, numbers and -_./ characters are allowed.
type: str
pool_type:
description: >
Expand Down Expand Up @@ -100,7 +101,7 @@
exclusively when you need to update the global pool's name.
type: str
reserve_pool_details:
reserve_pool_details:
description: Reserved IP subpool details from the global pool.
type: dict
suboptions:
Expand All @@ -111,10 +112,10 @@
type: str
name:
description:
- Name of the reserve IP subpool.
- Required for the operations in the Reserve IP Pool.
- Length should be less than or equal to 100.
- Only letters, numbers and -_./ characters are allowed.
- Name of the reserve IP subpool.
- Required for the operations in the Reserve IP Pool.
- Length should be less than or equal to 100.
- Only letters, numbers and -_./ characters are allowed.
type: str
pool_type:
description: Type of the reserve ip sub pool.
Expand All @@ -139,13 +140,13 @@
type: bool
ipv4_global_pool:
description:
- IP v4 Global pool address with cidr, example 175.175.0.0/16.
- If both 'ipv6_global_pool' and 'ipv4_global_pool_name' are provided, the 'ipv4_global_pool' will be given priority.
- IP v4 Global pool address with cidr, example 175.175.0.0/16.
- If both 'ipv6_global_pool' and 'ipv4_global_pool_name' are provided, the 'ipv4_global_pool' will be given priority.
type: str
ipv4_global_pool_name:
description:
- Specifies the name to be associated with the IPv4 Global IP Pool.
- If both 'ipv4_global_pool' and 'ipv4_global_pool_name' are provided, the 'ipv4_global_pool' will be given priority.
- Specifies the name to be associated with the IPv4 Global IP Pool.
- If both 'ipv4_global_pool' and 'ipv4_global_pool_name' are provided, the 'ipv4_global_pool' will be given priority.
type: str
version_added: 6.14.0
ipv4_subnet:
Expand Down Expand Up @@ -191,14 +192,14 @@
type: str
ipv6_global_pool:
description:
- The ipv6_global_pool is a required when the ipv6_address_space is set to true.
- It specifies the global IPv6 address pool using CIDR notation, such as "2001:db8:85a3::/64".
- In cases where both ipv6_global_pool and ipv6_global_pool_name are specified, ipv6_global_pool will take precedence.
- The ipv6_global_pool is a required when the ipv6_address_space is set to true.
- It specifies the global IPv6 address pool using CIDR notation, such as "2001:db8:85a3::/64".
- In cases where both ipv6_global_pool and ipv6_global_pool_name are specified, ipv6_global_pool will take precedence.
type: str
ipv6_global_pool_name:
description:
- Specifies the name assigned to the Ip v6 Global IP Pool.
- If both 'ipv6_global_pool' and 'ipv6_global_pool_name' are provided, the 'ipv6_global_pool' will be given priority.
- Specifies the name assigned to the Ip v6 Global IP Pool.
- If both 'ipv6_global_pool' and 'ipv6_global_pool_name' are provided, the 'ipv6_global_pool' will be given priority.
type: str
version_added: 6.14.0
ipv6_subnet:
Expand Down Expand Up @@ -226,7 +227,7 @@
Allows devices on IPv6 networks to self-configure their
IP addresses autonomously, eliminating the need for manual setup.
type: bool
network_management_details:
network_management_details:
description: Set default network settings for the site
type: list
elements: dict
Expand Down Expand Up @@ -536,6 +537,25 @@
- site_name: string
name: string
- name: Delete Global Pool
cisco.dnac.network_settings_workflow_manager:
dnac_host: "{{ dnac_host }}"
dnac_port: "{{ dnac_port }}"
dnac_username: "{{ dnac_username }}"
dnac_password: "{{ dnac_password }}"
dnac_verify: "{{ dnac_verify }}"
dnac_debug: "{{ dnac_debug }}"
dnac_version: "{{ dnac_version }}"
dnac_log_level: "{{ dnac_log_level }}"
dnac_log: true
state: deleted
config_verify: true
config:
- global_pool_details:
settings:
ip_pool:
- name: string
- name: Manage the network functions
cisco.dnac.network_settings_workflow_manager:
dnac_host: "{{dnac_host}}"
Expand Down Expand Up @@ -1307,14 +1327,30 @@ def get_aaa_settings_for_site(self, site_name, site_id):
params={"id": site_id}
)
# Extract AAA network and client/endpoint settings
network_aaa = aaa_network_response.get("response", {}).get("aaaNetwork")
client_and_endpoint_aaa = aaa_network_response.get("response", {}).get("aaaClient")
response = aaa_network_response.get("response", {})
network_aaa = response.get("aaaNetwork")
client_and_endpoint_aaa = response.get("aaaClient")

if not network_aaa or not client_and_endpoint_aaa:
self.log("No AAA settings found for site '{0}' (ID: {1})".format(site_name, site_id), "WARNING")
return None, None
missing = []
if not network_aaa:
missing.append("network_aaa")
if not client_and_endpoint_aaa:
missing.append("client_and_endpoint_aaa")
self.log(
"No {0} settings found for site '{1}' (ID: {2})".format(
" and ".join(missing), site_name, site_id
),
"WARNING",
)
return network_aaa, client_and_endpoint_aaa

self.log("Successfully retrieved AAA Network settings for site '{0}' (ID: {1}): {2}".format(site_name, site_id, network_aaa), "DEBUG")
self.log(
"Successfully retrieved AAA Network settings for site '{0}' (ID: {1}): {2}".format(
site_name, site_id, network_aaa
),
"DEBUG",
)
self.log("Successfully retrieved AAA Client and Endpoint settings for site '{0}' (ID: {1}): {2}"
.format(site_name, site_id, client_and_endpoint_aaa), "DEBUG")
except Exception as e:
Expand Down Expand Up @@ -1905,30 +1941,34 @@ def get_have_global_pool(self, global_pool_details):

global_pool = []
global_pool_index = 0
errors = [] # To collect all error messages

for pool_details in global_pool_ippool:
name = pool_details.get("name")
if name is None:
self.msg = "Missing required parameter 'name' in global_pool_details"
self.status = "failed"
return self
errors.append("Missing required parameter 'name' in global_pool_details: {}".format(pool_details))
continue

name_length = len(name)
if name_length > 100:
self.msg = "The length of the '{0}' in global_pool_details should be less or equal to 100. Invalid_config: {1}".format(name, pool_details)
self.status = "failed"
return self
errors.append("The length of the 'name' in global_pool_details should be less or equal to 100. Invalid_config: {}".format(pool_details))

if " " in name:
self.msg = "The 'name' in global_pool_details should not contain any spaces."
self.status = "failed"
return self
errors.append("The 'name' in global_pool_details should not contain any spaces. Invalid_config: {}".format(pool_details))

pattern = r'^[\w\-./]+$'
if not re.match(pattern, name):
self.msg = "The 'name' in global_pool_details should contain only letters, numbers and -_./ characters."
self.status = "failed"
return self
errors.append("The 'name' in global_pool_details should contain only letters, numbers, and -_./ characters. Invalid_config: {}"
.format(pool_details))

if errors:
# If there are errors, return a failure status with all messages
self.msg = "Validation failed with the following errors:\n" + "\n".join(errors)
self.status = "failed"
return self

for pool_details in global_pool_ippool:
name = pool_details.get("name")
# If the Global Pool doesn't exist and a previous name is provided
# Else try using the previous name
global_pool.append(self.global_pool_exists(name))
Expand Down Expand Up @@ -2197,7 +2237,12 @@ def get_want_global_pool(self, global_ippool):
"gateway": pool_details.get("gateway"),
"type": pool_details.get("pool_type"),
}
ip_address_space = pool_details.get("ip_address_space")
ip_address_space = pool_details.get("ip_address_space", "").upper()
if ip_address_space == "IPV4":
ip_address_space = "IPv4"
elif ip_address_space == "IPV6":
ip_address_space = "IPv6"

if not ip_address_space:
self.msg = "Missing required parameter 'ip_address_space' under global_pool_details."
self.status = "failed"
Expand Down Expand Up @@ -2337,8 +2382,6 @@ def get_want_reserve_pool(self, reserve_pool):
pool_values.update({"ipv4DnsServers": []})
if pool_values.get("ipv6AddressSpace") is None:
pool_values.update({"ipv6AddressSpace": False})
if pool_values.get("slaacSupport") is None:
pool_values.update({"slaacSupport": True})
if pool_values.get("ipv4TotalHost") is None:
del pool_values['ipv4TotalHost']
if pool_values.get("ipv6AddressSpace") is True:
Expand Down
Loading

0 comments on commit 4f0286d

Please sign in to comment.