Device Onboarding Guide
Introduction
Cisco DNA Center API can be used to provision configurations to the devices it manages. It supports network configurations that belong to the site the device is part of and also templates that can be deployed to devices, these templates support variables that can be replaced at the moment of deployment.
Templates can be used for Day Zero Provisioning, in order to send configuration for devices that are being onboarded to the network and Cisco DNA Center.
Goal
The goals of this guide are:
- Create Day0 template
- Create a network profile
- Assign sites to a network profile
- Add a device to Cisco DNA Center
- Claim the device
Endpoints and methods used
- POST
/dna/system/api/v1/auth/token
- GET
/dna/intent/api/v1/site
- POST
/dna/intent/api/v1/template-programmer/project
- POST
/dna/intent/api/v1/template-programmer/project/{project_id}/template
- GET
/dna/intent/api/v1/task/{task_id}
- POST
/dna/intent/api/v1/template-programmer/template/version
- POST
/api/v1/siteprofile
- POST
/api/v1/siteprofile/{site_profile_id}/site/{site_id}
- POST
/dna/intent/api/v1/onboarding/pnp-device/import
- POST
/dna/intent/api/v1/onboarding/pnp-device/site-claim
Prerequisites
For this guide, it is recommended that the developer is familiar with authenticating to Cisco DNA Center API, Site and Device management
Environment
This guide was developed using:
Authentication
First, we need to authenticate and retrieve a token from the API.
Do not use
verify=False
orurllib3.disable_warnings()
if you are not sure of its purpose. Read Authentication and Authorization.
import requests
from requests.auth import HTTPBasicAuth
import urllib3
urllib3.disable_warnings()
BASE_URL = 'https://<IP Address>'
AUTH_URL = '/api/system/v1/auth/token'
USERNAME = '<USERNAME>'
PASSWORD = '<PASSWORD>'
response = requests.post(BASE_URL + AUTH_URL, auth=HTTPBasicAuth(USERNAME, PASSWORD), verify=False)
token = response.json()['Token']
Site Information
With the token information, we proceed to retrieve the information of the DNA Center Guide Building site in the Cisco DNA Center, that later will be used for the site profile information.
SITE_URL = '/dna/intent/api/v1/site'
site_name = 'DNA Center Guide Building'
site_id = ''
response = requests.get(BASE_URL + SITE_URL,
headers=headers, verify=False)
for site in response.json()['response']:
if site['name'] == site_name:
site_id = site['id']
Configuration Template API
The Configuration Template API is used to interact with the Template Programmer functionality within DNA Center. It is a powerful API used to manage configuration projects, templates and deploy templates to devices. Templates support variables that can be set when calling deployment endpoint.
Day Zero templates need to belong to the Onboarding Configuration Project in the Template Editor.
Project Management
Projects group templates for easier management. We will use the API to assign the device to the Onboarding Configuration Project, for which the projectId
is needed.
TEMPLATE_PROJECT_URL = '/dna/intent/api/v1/template-programmer/project'
response = requests.get(BASE_URL + TEMPLATE_PROJECT_URL,
headers=headers,
verify=False)
project_name = "Onboarding Configuration"
response = get_configuration_template_project(headers)
project_id = ''
for project in response.json['response']:
if project['name'] == project_name:
project_id = project['id']
Template Management
Templates enable the developer to deploy configurations to the devices managed by Cisco DNA Center, keeping different versions if needed and making them more dynamic by using variables in the definition. In this guide we will create a template with two variables.
We will create a template and after that, we will retrieve the templateId
in order to commit it, which makes it possible to use it to deploy to devices.
template_info = {
"name": "DNA Center Guide",
"description": "Guide Configuration Template",
"tags": [],
"deviceTypes": [
{
"productFamily": "Routers",
"productSeries": "Cisco 1000 Series Integrated Services Routers"
}
],
"softwareType": "IOS-XE",
"softwareVariant": "XE",
"templateContent": "ip access-list extended $permitACLName\npermit ip 10.0.0.0 0.255.255.25.0 any\npermit ip 172.16.0.0 0.15.255.255 any\npermit ip 192.168.0.0 0.0.255.255 any\n!\n\nip access-list extended $denyACLName\ndeny ip 10.0.0.0 0.255.255.25.0 any\ndeny ip 172.16.0.0 0.15.255.255 any\ndeny ip 192.168.0.0 0.0.255.255 any\n!\n",
"rollbackTemplateContent": "",
"templateParams": [
{
"parameterName": "permitACLName",
"dataType": "STRING",
"defaultValue": None,
"description": None,
"required": True,
"notParam": False,
"paramArray": False,
"displayName": None,
"instructionText": None,
"group": None,
"order": 1,
"selection": {
"selectionType": None,
"selectionValues": {},
"defaultSelectedValues": []
},
"range": [],
"key": None,
"provider": None,
"binding": ""
},
{
"parameterName": "denyACLName",
"dataType": "STRING",
"defaultValue": None,
"description": None,
"required": True,
"notParam": False,
"paramArray": False,
"displayName": None,
"instructionText": None,
"group": None,
"order": 2,
"selection": {
"selectionType": None,
"selectionValues": {},
"defaultSelectedValues": []
},
"range": [],
"key": None,
"provider": None,
"binding": ""
}
],
"rollbackTemplateParams": [],
"composite": False,
"containingTemplates": []
}
TEMPLATE_URL = '/dna/intent/api/v1/template-programmer/project/{project_id}/template'
response = requests.post(BASE_URL + TEMPLATE_URL.format(project_id=project_id),
headers=headers, json=template,
verify=False)
task_id = response.json()['response']['taskId']
time.sleep(3)
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'
response = requests.get(BASE_URL + TASK_BY_ID_URL.format(task_id=task_id),
headers=headers, verify=False)
template_id = response.json()['response']['data']
After the template creation, it is needed to commit it in order to have a version suitable for deployment to a device.
template_version = {
"comments": "DNAC Guide Initial Version",
"templateId": template_id
}
TEMPLATE_VERSION_URL = '/dna/intent/api/v1/template-programmer/template/version'
response = requests.post(BASE_URL + TEMPLATE_VERSION_URL,
headers=headers, json=template_version,
verify=False)
Site Profile API
Site Profile API is used to make the relation between sites and templates, allowing to create a profile with Day Zero and Day N templates, and associate it with the kind of devices it can be used for and in which sites it can be used.
site_profile_info = {
"name": "DNA Center Guide Profile",
"namespace": "switching",
"profileAttributes": [
{
"key": "day0.templates",
"attribs": [
{
"key": "device.family",
"value": "Switches and Hubs",
"attribs": [
{
"key": "device.series",
"value": "Cisco Catalyst 9500 Series Switches",
"attribs": [
{
"key": "device.type",
"attribs": [
{
"key": "template.id",
"value": template_id,
},
{
"key": "device.tag",
"value": "",
"attribs": [
]
}
]
}
]
}
]
}
]
}
]
}
SITE_PROFILE_URL = '/api/v1/siteprofile'
response = requests.post(BASE_URL + SITE_PROFILE_URL,
headers=headers, json=site_profile_info,
verify=False
task_id = response.json()['response']['taskId']
time.sleep(3)
response = requests.get(BASE_URL + TASK_BY_ID_URL.format(task_id=task_id),
headers=headers, verify=False)
site_profile_id = response['id']
Onboarding (PnP) API
The Device Onboarding API supports the PnP process, giving the developer the option to create a workflow that detects when a device joins the network and communicates with Cisco DNA Center, and then sending the onboarding configuration to the device.
This API is composed of 28 endpoints, that can be used to manage workflows, include devices in the PnP Process, claim devices, amongst other things.
In this guide we will add a device with serial number 919L3GOS8QC
to the PnP process.
ONBOARDING_PNP_IMPORT_URL = '/dna/intent/api/v1/onboarding/pnp-device/import'
device_serial = '919L3GOS8QC'
device_name = 'CAT9k-DNAC-Guide'
device_pid = 'C9500-40X'
pnp_import_info = [
{
"deviceInfo": {
"hostname": device_name,
"serialNumber": device_serial,
"pid": device_pid,
"sudiRequired": False,
"userSudiSerialNos": [],
"aaaCredentials": {
"username": "",
"password": ""
}
}
}
]
response = requests.post(BASE_URL + ONBOARDING_PNP_IMPORT_URL,
headers=headers, json=pnp_import_info,
verify=False)
device_id = response['successList'][0]['id']
After importing creating the site profile and importing the device to the PnP process, the final step is to claim the device with the configuration and the site.
claim_info = {
"siteId": site_id,
"deviceId": device_id,
"type": "Default",
"configInfo": {
"configId": template_id,
"configParameters": [
{"key": "permitACLName", "value": "GUIDE-ALLOW-ACL"},
{"key": "denyACLName", "value": "GUIDE - DENY - ACL"}
]
}
}
response = requests.post(BASE_URL + ONBOARDING_CLAIM_DEVICE_URL,
headers=headers, json=claim_info,
verify=False
Code
The repository for this guide is here. The final code with functions is shown below.
# Modules import
import requests
from requests.auth import HTTPBasicAuth
import time
import sys
# Disable SSL warnings. Not needed in production environments with valid certificates
import urllib3
urllib3.disable_warnings()
# Authentication
BASE_URL = 'https://<IP Address>'
AUTH_URL = '/api/system/v1/auth/token'
USERNAME = '<USERNAME>'
PASSWORD = '<PASSWORD>'
# URLs
SITE_PROFILE_ADD_SITE_URL = '/api/v1/siteprofile/{site_profile_id}/site/{site_id}'
SITE_PROFILE_URL = '/api/v1/siteprofile'
SITE_URL = '/dna/intent/api/v1/site'
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'
TEMPLATE_PROJECT_URL = '/dna/intent/api/v1/template-programmer/project'
TEMPLATE_URL = '/dna/intent/api/v1/template-programmer/project/{project_id}/template'
TEMPLATE_VERSION_URL = '/dna/intent/api/v1/template-programmer/template/version'
ONBOARDING_PNP_IMPORT_URL = '/dna/intent/api/v1/onboarding/pnp-device/import'
ONBOARDING_CLAIM_DEVICE_URL = '/dna/intent/api/v1/onboarding/pnp-device/site-claim'
# Get Authentication token
def get_dnac_jwt_token():
response = requests.post(BASE_URL + AUTH_URL,
auth=HTTPBasicAuth(USERNAME, PASSWORD),
verify=False)
token = response.json()['Token']
return token
# Get list of sites
def get_sites(headers):
response = requests.get(BASE_URL + SITE_URL,
headers=headers, verify=False)
return response.json()['response']
# Get template configuration project
def get_configuration_template_project(headers):
response = requests.get(BASE_URL + TEMPLATE_PROJECT_URL,
headers=headers,
verify=False)
return response.json()
# Create template
def create_configuration_template(headers, project_id, template):
response = requests.post(BASE_URL + TEMPLATE_URL.format(project_id=project_id),
headers=headers, json=template,
verify=False)
return response.json()['response']
# Create configuration template version
def create_configuration_template_version(headers, template_version):
response = requests.post(BASE_URL + TEMPLATE_VERSION_URL,
headers=headers, json=template_version,
verify=False)
return response.json()['response']
# Create site profile
def create_site_profile(headers, site_profile_info):
response = requests.post(BASE_URL + SITE_PROFILE_URL,
headers=headers, json=site_profile_info,
verify=False)
return response.json()['response']
# Assign Site to Site Profile
def assign_site_to_site_profile(headers, site_profile_id, site_id):
response = requests.post(BASE_URL + SITE_PROFILE_ADD_SITE_URL.format(
site_profile_id=site_profile_id,
site_id=site_id),
headers=headers, verify=False)
return response.json()
# Import device to PnP process
def import_device_to_pnp(headers, pnp_import_info):
response = requests.post(BASE_URL + ONBOARDING_PNP_IMPORT_URL,
headers=headers, json=pnp_import_info,
verify=False)
return response.json()
# Import device to PnP process
def claim_device_to_site(headers, claim_info):
response = requests.post(BASE_URL + ONBOARDING_CLAIM_DEVICE_URL,
headers=headers, json=claim_info,
verify=False)
return response.json()
# Get Task result
def get_task(headers, task_id):
response = requests.get(BASE_URL + TASK_BY_ID_URL.format(task_id=task_id),
headers=headers, verify=False)
return response.json()['response']
def main():
# obtain the Cisco DNA Center Auth Token
token = get_dnac_jwt_token()
headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'}
# Get Site ID
site_name = 'DNA Center Guide Building'
response = get_sites(headers)
for site in response:
if site['name'] == site_name:
site_id = site['id']
print('Printing site name "{site_name}" site id {site_id}'.format(site_name=site_name,
site_id=site_id))
# # Get Device IP and Name using Serial Number
device_serial = '919L3GOS8QC'
device_name = 'CAT9k-DNAC-Guide'
device_pid = 'C9500-40X'
# Get Project information
project_name = "Onboarding Configuration"
response = get_configuration_template_project(headers)
project_id = ''
for project in response:
if project['name'] == project_name:
project_id = project['id']
# Create Configuration Template
template_info = {
"name": "DNA Center Guide - Day0",
"description": "Guide Configuration Template",
"tags": [],
"deviceTypes": [
{
"productFamily": "Switches and Hubs",
"productSeries": "Cisco Catalyst 9500 Series Switches"
}
],
"softwareType": "IOS-XE",
"softwareVariant": "XE",
"templateContent": "ip access-list extended $permitACLName\npermit ip 10.0.0.0 0.255.255.25.0 any\npermit ip 172.16.0.0 0.15.255.255 any\npermit ip 192.168.0.0 0.0.255.255 any\n!\n\nip access-list extended $denyACLName\ndeny ip 10.0.0.0 0.255.255.25.0 any\ndeny ip 172.16.0.0 0.15.255.255 any\ndeny ip 192.168.0.0 0.0.255.255 any\n!\n",
"rollbackTemplateContent": "",
"templateParams": [
{
"parameterName": "permitACLName",
"dataType": "STRING",
"defaultValue": None,
"description": None,
"required": True,
"notParam": False,
"paramArray": False,
"displayName": None,
"instructionText": None,
"group": None,
"order": 1,
"selection": {
"selectionType": None,
"selectionValues": {},
"defaultSelectedValues": []
},
"range": [],
"key": None,
"provider": None,
"binding": ""
},
{
"parameterName": "denyACLName",
"dataType": "STRING",
"defaultValue": None,
"description": None,
"required": True,
"notParam": False,
"paramArray": False,
"displayName": None,
"instructionText": None,
"group": None,
"order": 2,
"selection": {
"selectionType": None,
"selectionValues": {},
"defaultSelectedValues": []
},
"range": [],
"key": None,
"provider": None,
"binding": ""
}
],
"rollbackTemplateParams": [],
"composite": False,
"containingTemplates": []
}
response = create_configuration_template(headers, project_id, template_info)
task_id = response['taskId']
time.sleep(3)
response = get_task(headers, task_id)
template_id = response['data']
# Create Template version
template_version = {
"comments": "DNAC Guide Day 0 Initial Version",
"templateId": template_id
}
create_configuration_template_version(headers, template_version)
# Create Configuration Template
site_profile_info = {
"name": "DNA Center Guide Profile",
"namespace": "switching",
"profileAttributes": [
{
"key": "day0.templates",
"attribs": [
{
"key": "device.family",
"value": "Switches and Hubs",
"attribs": [
{
"key": "device.series",
"value": "Cisco Catalyst 9500 Series Switches",
"attribs": [
{
"key": "device.type",
"attribs": [
{
"key": "template.id",
"value": template_id,
},
{
"key": "device.tag",
"value": "",
"attribs": [
]
}
]
}
]
}
]
}
]
}
]
}
response = create_site_profile(headers, site_profile_info)
task_id = response['taskId']
time.sleep(3)
response = get_task(headers, task_id)
site_profile_id = response['id']
response = assign_site_to_site_profile(headers, site_profile_id, site_id)
# Add device to PnP process
pnp_import_info = [
{
"deviceInfo": {
"hostname": device_name,
"serialNumber": device_serial,
"pid": device_pid,
"sudiRequired": False,
"userSudiSerialNos": [],
"aaaCredentials": {
"username": "",
"password": ""
}
}
}
]
response = import_device_to_pnp(headers, pnp_import_info)
device_id = response['successList'][0]['id']
claim_info = {
"siteId": site_id,
"deviceId": device_id,
"type": "Default",
"configInfo": {
"configId": template_id,
"configParameters": [
{"key": "permitACLName", "value": "GUIDE-ALLOW-ACL"},
{"key": "denyACLName", "value": "GUIDE - DENY - ACL"}
]
}
}
claim_device_to_site(headers, claim_info)
if __name__ == "__main__":
main()