-
Notifications
You must be signed in to change notification settings - Fork 0
Provision Enhancement #111
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
provisioning | ||
- API to re-provision provisioned devices | ||
- API to un-provision provisioned devices | ||
- Un-provisioning refers to removing a device from the inventory list | ||
version_added: '6.6.0' | ||
extends_documentation_fragment: | ||
- cisco.dnac.workflow_manager_params | ||
|
@@ -56,6 +57,7 @@ | |
only. | ||
- Set to 'true' to proceed with provisioning | ||
to a site. | ||
- only applicable for wired devices. | ||
type: bool | ||
required: false | ||
default: true | ||
|
@@ -198,6 +200,44 @@ | |
- Must be either 5, 15 or 25 representing | ||
the proportion of APs to reboot at once. | ||
type: int | ||
ap_authorization_list_name: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
description: | | ||
- The name of the Access Point (AP) authorization | ||
list to be used during WLC provisioning. | ||
type: str | ||
authorize_mesh_and_non_mesh_aps: | ||
description: | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
- A flag that indicates whether to authorize | ||
both mesh and non-mesh Access Points (APs) | ||
during the WLC provisioning process. | ||
type: bool | ||
feature_template: | ||
description: | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
- A list of feature templates to be applied | ||
- Each entry represents a feature template | ||
with associated configuration details. | ||
type: dict | ||
suboptions: | ||
design_name: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
description: The name of the feature template. | ||
type: str | ||
additional_identifiers: | ||
description: A list of additional identifiers | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make it more descriptive, give examples. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
for the feature template. | ||
type: list | ||
elements: dict | ||
suboptions: | ||
wlan_profile_name: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
description: The WLAN profile name. | ||
type: str | ||
site_name_hierarchy: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
description: The site name hierarchy. | ||
type: str | ||
excluded_attributes: | ||
description: A list of attributes to be excluded | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make it more descriptive, give examples. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
from the feature template. | ||
type: list | ||
elements: str | ||
application_telemetry: | ||
description: | | ||
- A list of settings for enabling or disabling application telemetry on a group of network devices. | ||
|
@@ -307,6 +347,9 @@ | |
rolling_ap_upgrade: | ||
enable_rolling_ap_upgrade: false | ||
ap_reboot_percentage: 5 | ||
ap_authorization_list_name: "AP-Auth-List" | ||
authorize_mesh_and_non_mesh_aps: true | ||
|
||
- name: Provision a wired device to a site | ||
cisco.dnac.provision_workflow_manager: | ||
dnac_host: "{{dnac_host}}" | ||
|
@@ -455,6 +498,33 @@ | |
- application_telemetry: | ||
- device_ips: ["204.1.1.2", "204.192.6.200"] | ||
telemetry: disable | ||
|
||
- name: Provision a wireless device to a site with feature template | ||
cisco.dnac.provision_workflow_manager: | ||
dnac_host: "{{ dnac_host }}" | ||
dnac_username: "{{ dnac_username }}" | ||
dnac_password: "{{ dnac_password }}" | ||
dnac_verify: "{{ dnac_verify }}" | ||
dnac_port: "{{ dnac_port }}" | ||
dnac_version: "{{ dnac_version }}" | ||
dnac_debug: "{{ dnac_debug }}" | ||
dnac_log: true | ||
dnac_log_level: DEBUG | ||
config_verify: false | ||
dnac_api_task_timeout: 1000 | ||
dnac_task_poll_interval: 1 | ||
state: merged | ||
config: | ||
- site_name_hierarchy: Global/USA/SAN JOSE/BLD23 | ||
management_ip_address: 204.192.4.2 | ||
primary_managed_ap_locations: | ||
- Global/USA/SAN JOSE/BLD23/FLOOR1_LEVEL2 | ||
feature_template: | ||
- design_name: newtest | ||
additional_identifiers: | ||
wlan_profile_name: ARUBA_SSID_profile | ||
site_name_hierarchy: Global/USA/SAN JOSE/BLD23 | ||
excluded_attributes: [example, test] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Give example from a real test. |
||
""" | ||
RETURN = r""" | ||
# Case_1: Successful creation/updation/deletion of provision | ||
|
@@ -561,6 +631,8 @@ def validate_input(self, state=None): | |
}, | ||
"skip_ap_provision": {"type": "bool", "required": False}, | ||
"rolling_ap_upgrade": {"type": "dict", "required": False}, | ||
"ap_authorization_list_name": {"type": "str", "required": False}, | ||
"authorize_mesh_and_non_mesh_aps": {"type": "bool", "required": False, "default": False}, | ||
"provisioning": {"type": "bool", "required": False, "default": True}, | ||
"force_provisioning": {"type": "bool", "required": False, "default": False}, | ||
"clean_config": {"type": "bool", "required": False, "default": False}, | ||
|
@@ -578,6 +650,20 @@ def validate_input(self, state=None): | |
}, | ||
}, | ||
}, | ||
"feature_template": { | ||
"type": "list", | ||
"elements": "dict", | ||
"options": { | ||
"design_name": {"type": "str", "required": True}, | ||
"attributes": {"type": "dict", "required": True}, | ||
"additional_identifiers": {"type": "dict", "required": False}, | ||
"excluded_attributes": { | ||
"type": "list", | ||
"elements": "str", | ||
"required": False, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
if state == "merged": | ||
|
@@ -1392,6 +1478,10 @@ def get_wireless_params(self): | |
if self.validated_config.get("rolling_ap_upgrade"): | ||
rolling_ap_upgrade = self.validated_config["rolling_ap_upgrade"] | ||
wireless_params[0]["rolling_ap_upgrade"] = rolling_ap_upgrade | ||
if self.validated_config.get("ap_authorization_list_name"): | ||
wireless_params[0]["ap_authorization_list_name"] = self.validated_config.get("ap_authorization_list_name") | ||
if self.validated_config.get("authorize_mesh_and_non_mesh_aps") is not None: | ||
wireless_params[0]["authorize_mesh_and_non_mesh_aps"] = self.validated_config.get("authorize_mesh_and_non_mesh_aps") | ||
|
||
response = self.dnac_apply["exec"]( | ||
family="devices", | ||
|
@@ -1413,8 +1503,83 @@ def get_wireless_params(self): | |
), | ||
"INFO", | ||
) | ||
|
||
if self.validated_config.get("feature_template"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
feature_templates = self.validated_config.get("feature_template") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we need to check feature_templates?
|
||
wireless_params[0]["feature_template"] = [] | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
for template in feature_templates: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. User enumerate, so we can print the index..
|
||
design_name = template.get("design_name") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. design_name is required field? If yes, can we check and udpate?
|
||
attributes = template.get("attributes", []) | ||
cleaned_attributes = [] | ||
|
||
if isinstance(attributes, dict): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
for key, value in attributes.items(): | ||
cleaned_attributes.append({ | ||
"name": key, | ||
"value": value | ||
}) | ||
else: | ||
self.log(f"Expected 'attributes' to be a dict, got: {type(attributes)}", "WARNING") | ||
|
||
additional_identifiers = template.get("additional_identifiers", {}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we need to process additonal_identifiers?
|
||
excluded_attributes = template.get("excluded_attributes", []) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
ft_entry = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
"design_name": design_name, | ||
"attributes": cleaned_attributes | ||
} | ||
|
||
if additional_identifiers: | ||
ft_entry["additional_identifiers"] = additional_identifiers | ||
if excluded_attributes: | ||
ft_entry["excluded_attributes"] = excluded_attributes | ||
|
||
wireless_params[0]["feature_template"].append(ft_entry) | ||
|
||
self.log( | ||
"Parameters collected for the provisioning of wireless device: {0}".format(wireless_params), | ||
"INFO", | ||
) | ||
return wireless_params | ||
|
||
def resolve_template_id(self, design_name): | ||
""" | ||
Retrieves the feature template ID for a given design name. | ||
|
||
Args: | ||
design_name (str): Name of the feature template design to match. | ||
|
||
Returns: | ||
str or None: The featureTemplateId if found, else None. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
try: | ||
ft_response = self.dnac_apply["exec"]( | ||
family="wireless", | ||
function="get_feature_template_summary", | ||
params={'designName': design_name} | ||
) | ||
|
||
self.log("Feature template response: {0}".format(str(ft_response)), "DEBUG") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
for template_group in ft_response.get("response", []): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
for instance in template_group.get("instances", []): | ||
if ( | ||
instance.get("designName") == design_name | ||
and not instance.get("systemTemplate", False) | ||
): | ||
template_id = instance.get("id") | ||
self.log("Resolved featureTemplateId: {0} for designName: '{1}'".format(template_id, design_name), "INFO") | ||
return template_id | ||
|
||
self.log("Feature template with designName '{0}' not found.".format(design_name), "WARNING") | ||
return None | ||
|
||
except Exception as e: | ||
msg = "Failed to resolve featureTemplateId for designName '{0}': {1}".format(design_name, str(e)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
self.log(msg, "ERROR") | ||
return None | ||
|
||
def get_want(self, config): | ||
""" | ||
Get all provision related informantion from the playbook | ||
|
@@ -1778,18 +1943,47 @@ def application_telemetry(self, telemetry_config): | |
"disable": "disable_application_telemetry_feature_on_multiple_network_devices" | ||
} | ||
|
||
self.log("Starting application telemetry configuration process", "DEBUG") | ||
self.log("Received telemetry configuration: {0}".format(telemetry_config), "DEBUG") | ||
|
||
application_telemetry_details = telemetry_config.get("application_telemetry", []) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to check the value of application_telemetry_details? If not required, ignore this comment
|
||
self.log("Processing {0} telemetry configuration entries".format(len(application_telemetry_details)), "INFO") | ||
|
||
for detail in application_telemetry_details: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
device_ips = detail.get("device_ips", []) | ||
self.log("Processing device IPs: {0}".format(device_ips), "DEBUG") | ||
if device_ips is None or len(device_ips) == 0: | ||
self.msg = "No valid device IPs provided for application telemetry." | ||
self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status() | ||
return self | ||
|
||
all_empty = True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
for ip in device_ips: | ||
if ip.strip() != "": | ||
all_empty = False | ||
self.log("Valid device IP found: {0}".format(ip), "DEBUG") | ||
break | ||
|
||
if all_empty: | ||
self.msg = "No valid device IPs provided for application telemetry." | ||
self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status() | ||
return self | ||
|
||
telemetry = detail.get("telemetry") # "enable" or "disable" | ||
if telemetry not in ["enable", "disable"]: | ||
self.msg = "Invalid telemetry action '{0}'. Expected 'enable' or 'disable'.".format(telemetry) | ||
self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status() | ||
wlan_mode = detail.get("wlan_mode") | ||
include_guest_ssid = detail.get("include_guest_ssid", False) | ||
|
||
self.log("Telemetry action: {0}, WLAN mode: {1}, Include guest SSID: {2}".format( | ||
telemetry, wlan_mode, include_guest_ssid | ||
), "DEBUG") | ||
for ip in device_ips: | ||
self.validated_config["management_ip_address"] = ip | ||
device_type, device_family = self.get_device_type_and_family(ip) | ||
self.log("Device type: {0}, Device family: {1} for IP: {2}".format( | ||
device_type, device_family, ip | ||
), "DEBUG") | ||
|
||
unsupported_devices = [ | ||
"Cisco Catalyst 9500 Switch", | ||
|
@@ -3052,6 +3246,54 @@ def provision_wireless_device(self): | |
) | ||
payload["rollingApUpgrade"] = rolling_ap_upgrade | ||
|
||
if "ap_authorization_list_name" in prov_params: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
payload["apAuthorizationListName"] = prov_params.get("ap_authorization_list_name") | ||
|
||
if "authorize_mesh_and_non_mesh_aps" in prov_params: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
payload["authorizeMeshAndNonMeshAPs"] = prov_params.get("authorize_mesh_and_non_mesh_aps") | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if self.compare_dnac_versions(self.get_ccc_version(), "3.1.3.0") >= 0: | ||
self.log("Catalyst Center version >= 3.1.3.0 — processing 'feature_template'", "INFO") | ||
self.log(prov_params, "DEBUG") | ||
if "feature_template" in prov_params: | ||
ft = prov_params.get("feature_template", [])[0] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we need to check ft before it is being used?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As we have many statements in else part, we can have a separate API to do it..
|
||
self.log("Feature template data: {0}".format(ft), "DEBUG") | ||
wlan_profile = ft["additional_identifiers"]["wlan_profile_name"] | ||
site_hierarchy = ft["additional_identifiers"]["site_name_hierarchy"] | ||
excluded = ft.get("excluded_attributes", []) | ||
|
||
feature_template_id = self.resolve_template_id(ft["design_name"]) | ||
site_exists, site_id = self.get_site_id(site_hierarchy) | ||
self.log(site_id, "DEBUG") | ||
site_uuid = site_id | ||
|
||
new_entry = { | ||
"featureTemplateId": feature_template_id, | ||
"attributes": { | ||
}, | ||
"additionalIdentifiers": { | ||
"wlanProfileName": wlan_profile, | ||
"siteUuid": site_uuid | ||
}, | ||
"excludedAttributes": excluded | ||
} | ||
|
||
if "featureTemplatesOverridenAttributes" not in payload: | ||
payload["featureTemplatesOverridenAttributes"] = { | ||
"editFeatureTemplates": [] | ||
} | ||
|
||
payload["featureTemplatesOverridenAttributes"]["editFeatureTemplates"].append(new_entry) | ||
else: | ||
self.log("Catalyst Center version < 3.1.3.0 — skipping 'feature_template'", "INFO") | ||
|
||
import json | ||
|
||
self.log( | ||
"Final constructed payload:\n{0}".format(json.dumps(payload, indent=2)), | ||
"INFO", | ||
) | ||
|
||
try: | ||
response = self.dnac_apply["exec"]( | ||
family="wireless", | ||
|
@@ -3126,10 +3368,11 @@ def get_diff_deleted(self): | |
self.set_operation_result("success", False, self.msg, "INFO") | ||
return self | ||
|
||
if device_type != "wired": | ||
self.result["msg"] = "APIs are not supported for the device" | ||
self.log(self.result["msg"], "CRITICAL") | ||
return self | ||
if self.compare_dnac_versions(self.get_ccc_version(), "2.3.7.6") <= 0: | ||
if device_type != "wired": | ||
self.result["msg"] = "APIs are not supported for the device" | ||
self.log(self.result["msg"], "CRITICAL") | ||
return self | ||
|
||
device_id = self.get_device_id() | ||
provision_id, status = self.get_device_provision_status(device_id) | ||
|
@@ -3139,7 +3382,7 @@ def get_diff_deleted(self): | |
"Device associated with the passed IP address is not provisioned" | ||
) | ||
self.log(self.result["msg"], "CRITICAL") | ||
self.result["response"] = self.want["prov_params"] | ||
self.result["response"] = self.result["msg"] | ||
return self | ||
|
||
if self.compare_dnac_versions(self.get_ccc_version(), "2.3.5.3") <= 0: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing support for multiple wireless fields:
"type": "boolean",
"required": false,
"name": "skipApProvision",
"displayText": "Skip AP Provision",
"description": "True if Skip AP Provision is enabled, else False"
},
{
"type": "map",
"address": "uuidfa2e4f30",
"required": false,
"name": "rollingApUpgrade",
"displayText": "Rolling AP Upgrade",
"description": "Rolling AP Upgrade"
},
{
"type": "string",
"enum": [],
"sensitive": false,
"default": "",
"constraints": [],
"required": false,
"name": "apAuthorizationListName",
"displayText": "Ap Authorization List Name",
"description": "AP Authorization List name. 'Obtain the AP Authorization List names by using the API call GET: /intent/api/v1/wirelessSettings/apAuthorizationLists. During re-provision, obtain the AP Authorization List configured for the given provisioned network device Id using the API call GET: /intent/api/v1/wireless/apAuthorizationLists/{networkDeviceId}'"
},
{
"type": "boolean",
"required": false,
"name": "authorizeMeshAndNonMeshAccessPoints",
"displayText": "Authorize Mesh And Non Mesh Access Points",
"description": "True if AP Authorization List should authorize against All Mesh/Non-Mesh APs, else false if AP Authorization List should only authorize against Mesh APs (Applicable only when Mesh is enabled on sites)"
},
{
"type": "map",
"address": "uuid789146a0",
"required": false,
"name": "featureTemplatesOverridenAttributes",
"displayText": "Feature Templates Overriden Attributes"
}
I could not find all these supported on wireless controller provisioning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More missing: {
"type": "boolean",
"required": false,
"name": "enableRollingApUpgrade",
"displayText": "Enable Rolling AP Upgrade",
"description": "True if Rolling AP Upgrade is enabled, else False"
},
{
"type": "integer",
"constraints": [],
"required": false,
"name": "apRebootPercentage",
"displayText": "AP Reboot Percentage",
"description": "AP Reboot Percentage. Permissible values - 5, 15, 25"
}
],
"uuid789146a0": [
{
"type": "array",
"arrayType": "map",
"constraints": [],
"address": "uuid7a58402c",
"required": true,
"name": "editFeatureTemplates",
"displayText": "Edit Feature Templates",
"description": "This array consists of Feature Templates that need to be overridden during the provisioning process for the current provision instance. These edits will not alter the original designs of the Feature Templates but will only apply to the values for the current provisioning instance. Note: Locked attributes cannot be edited in the Provision API. Additionally, default feature templates ('systemTemplate') cannot be included in the payload, as they are not editable."
}
],