Device Provisioning 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.

Goal

The goals of this guide are:

  1. Assign a device to a site
  2. Provision network settings for the site
  3. Create a configuration template project
  4. Create a template within the project
  5. Provision a device with the template

Device provisioning workflow

Endpoints and methods used

  • POST /dna/system/api/v1/auth/token
  • GET /dna/intent/api/v1/site
  • GET /dna/intent/api/v1/network-device/serial-number/{serial_number}
  • POST /dna/intent/api/v1/network/{site_id}
  • POST /dna/intent/api/v1/site/{site_id}/device
  • GET /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 /dna/intent/api/v1/template-programmer/template/deploy

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 or urllib3.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 = '/dna/system/api/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 and Device Information

With the token information, we proceed to retrieve the information of the DNA Center Guide Building site in the Cisco DNA Center and the information from the device with serial number 919L3GOS8QC.

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']

DEVICES_BY_SERIAL_URL = '/dna/intent/api/v1/network-device/serial-number/{serial_number}'
serial_number = '919L3GOS8QC'
response = requests.get(BASE_URL + DEVICES_BY_SERIAL_URL.format(serial_number=serial_number),
             headers=headers,
             verify=False)
device_ip = response.json()['response']['managementIpAddress']
device_name = response.json()['response']['hostname']

Network API

The Network API is composed of 16 endpoints that can be used to create networks, assign settings, device credentials relevant to the network and several other configuration options.

In this guide, we are using the Network API to create a network with default DHCP, DNS and Syslog settings, that will be applied to devices that are part of the network.

NETWORK_URL = '/dna/intent/api/v1/network/{site_id}'
network = {
    "settings": {
        "dhcpServer": [
            "172.30.200.5"
        ],
        "dnsServer": {
            "domainName": "devnet.local",
            "primaryIpAddress": "172.30.200.6",
            "secondaryIpAddress": "172.30.200.7"
        },
        "syslogServer": {
            "ipAddresses": [
                "10.255.0.1"
            ],
            "configureDnacIP": True
        }
    }
}
response = requests.post(BASE_URL + NETWORK_URL.format(site_id=site_id),
                         headers=headers, json=network,
                         verify=False)

Site API

Using Site API, it is possible to assign a device to a site:

SITE_DEVICE_URL = '/dna/intent/api/v1/site/{site_id}/device'
site_devices = {
    'device': device_ips
}
response = requests.post(BASE_URL + SITE_DEVICE_URL.format(site_id=site_id),
                            headers=headers, json=site_devices,
                            verify=False)

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.

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)

Template deployment

Once the template is created and committed, it is available for deployment to a device, which can also be done using the Template API.

TEMPLATE_DEPLOY_URL = '/dna/intent/api/v1/template-programmer/template/deploy'
deployment_info = {
    "forcePushTemplate": False,
    "isComposite": False,
    "targetInfo": [
        {
            "hostName": device_name,
            "params": {
                "permitACLName": "GUIDE-ALLOW-ACL",
                "denyACLName": "GUIDE-DENY-ACL"
            },
            "type": "MANAGED_DEVICE_IP"
        }
    ],
    "templateId": template_id
}
response = requests.post(BASE_URL + TEMPLATE_DEPLOY_URL,
                         headers=headers, json=deployment_info,
                         verify=False)
print(response)

Code

The repository for this guide is here. The final code with functions is shown below.

# Authentication
BASE_URL = 'https://<IP Address>'
AUTH_URL = '/dna/system/api/v1/auth/token'
USERNAME = '<USERNAME>'
PASSWORD = '<PASSWORD>'

# URLs
DEVICES_BY_SERIAL_URL = '/dna/intent/api/v1/network-device/serial-number/{serial_number}'
NETWORK_URL = '/dna/intent/api/v1/network/{site_id}'
SITE_DEVICE_URL = '/dna/intent/api/v1/site/{site_id}/device'
SITE_URL = '/dna/intent/api/v1/site'
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'
TEMPLATE_DEPLOY_URL = '/dna/intent/api/v1/template-programmer/template/deploy'
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'

# 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 device by serial
def get_device_by_serial(headers, serial_number):
    response = requests.get(BASE_URL + DEVICES_BY_SERIAL_URL.format(serial_number=serial_number),
                            headers=headers,
                            verify=False)
    return response.json()['response']

# Add devices to site
def add_devices_site(headers, site_id, devices):
    headers['__runsync'] = 'true'
    headers['__runsynctimeout'] = '30'
    response = requests.post(BASE_URL + SITE_DEVICE_URL.format(site_id=site_id),
                             headers=headers, json=devices,
                             verify=False)
    return response.json()

# Create template configuration project
def create_network(headers, site_id, network):
    response = requests.post(BASE_URL + NETWORK_URL.format(site_id=site_id),
                             headers=headers, json=network,
                             verify=False)
    return response.json()

# 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']

# Deploy template
def deploy_configuration_template(headers, deployment_info):
    response = requests.post(BASE_URL + TEMPLATE_DEPLOY_URL,
                             headers=headers, json=deployment_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
    serial_number = '919L3GOS8QC'
    response = get_device_by_serial(headers, serial_number)
    device_ip = response['managementIpAddress']
    device_name = response['hostname']
    device_ips = [device_ip]

    print('\nPrinting device serial {serial_number} device IP {ip}'.format(serial_number=serial_number,
                                                                           ip=device_ip))
    # Create Site Network
    network = {
        "settings": {
            "dhcpServer": [
                "172.30.200.5"
            ],
            "dnsServer": {
                "domainName": "devnet.local",
                "primaryIpAddress": "172.30.200.6",
                "secondaryIpAddress": "172.30.200.7"
            },
            "syslogServer": {
                "ipAddresses": [
                    "10.255.0.1"
                ],
                "configureDnacIP": True
            }
        }
    }
    response = create_network(headers, site_id, network)

    site_devices = {
        'device': device_ips
    }

    # Assign device to site
    response = add_devices_site(headers, site_id, site_devices)
    print(response['result']['progress'])

    # 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",
        "description": "Guide Configuration Template",
        "tags": [],
        "deviceTypes": [
            {
                "productFamily": "Routers",
                "productSeries": "Cisco 1000 Series Integrated Services Routers"
            }
        ],
        "softwareType": "IOS-IOS",
        "softwareVariant": "IOS",
        "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 Initial Version",
        "templateId": template_id
    }
    create_configuration_template_version(headers, template_version)

    # Deploy Template to device
    deployment_info = {
        "forcePushTemplate": False,
        "isComposite": False,
        "targetInfo": [
            {
                "hostName": device_name,
                "params": {
                    "permitACLName": "GUIDE-ALLOW-ACL",
                    "denyACLName": "GUIDE-DENY-ACL"
                },
                "type": "MANAGED_DEVICE_IP"
            }
        ],
        "templateId": template_id
    }
    response = deploy_configuration_template(headers, deployment_info)
    print(response)


if __name__ == "__main__":
    main()