Software Image Management Guide

Introduction

Cisco DNA Center provides APIs to manage the operating system images of the devices registered to it, where the developer can upload, distribute and activate an image on a device.

Goal

The goals of this guide are:

  1. Get the device information
  2. Get a list of images filtered by family
  3. Distribute the golden image to the selected device
  4. Activate the golden image

SWIM workflow

Endpoints and methods used

  • GET /dna/intent/api/v1/network-device
  • GET /dna/intent/api/v1/image/importation
  • POST /dna/intent/api/v1/image/distribution
  • POST /dna/intent/api/v1/image/activation/device
  • GET /dna/intent/api/v1/task/{task_id}

Prerequisites

For this module, it is recommended that the user already has experience authenticating with Cisco DNA Center, getting devices information and asynchronous tasks:

Environment

This guide was developed using:

Software Image Management API

The software image management API is composed of 5 endpoints that support image uploading, distribution and activation.

Get Device Information

We are going to update the image on the CSR1Kv-03.devnet.local device.

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

With the token, we can procede to query the devices API to get the device ID of CSR1Kv-03.devnet.local:

# Get device
headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'}
query_params = {
    'hostname': 'CSR1Kv-03.devnet.local'
}
DEVICES_URL = '/dna/intent/api/v1/network-device'
response = requests.get(BASE_URL + DEVICES_URL,
                        params=query_params,
                        headers=headers, verify=False)
device_id = response.json()['response'][0]['id']

Image importation

The developer can use this endpoint to import an image, using HTTP or FTP, to Cisco DNA Center.

# Print software images
SOFTWARE_IMAGE_IMPORT_URL = '/dna/intent/api/v1/image/importation/source/url'
import_info = {
    "sourceURL": "http://10.104.49.64/cat3k_caa_universalk9.16.12.03a.SPA.bin"
}
response = requests.post(BASE_URL + SOFTWARE_IMAGE_IMPORT_URL,
                    json=import_info,
                    headers=headers, verify=False)
image_id = response.json()['response']

Image details

With this endpoint, it is possible to get the information available on Cisco DNA Center. In this case, we are filtering the results based on the family of the image.

# Print software images
SOFTWARE_IMAGE_URL='/dna/intent/api/v1/image/importation'
query_params ={
    'family': 'cat3k'
}
response = requests.get(BASE_URL + SOFTWARE_IMAGE_URL,
                    params=query_params,
                    headers=headers, verify=False)
image_id = response.json()['response'][0]['imageUuid']

Image Distribution

After getting the image, it is possible to distribute to the devices on the network, but first it is needed for the user to set the image as golden using the web interface.

For the image distribution it is needed to have the device ID and the image ID. This is an asynchronous API so in order to obtain the result, it is needed to query the task API.

SOFTWARE_IMAGE_DISTRIBUTION_URL = '/dna/intent/api/v1/image/distribution'
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'
distribution_info = [
        {
    'deviceUuid': device_id,
    'imageUuid': image_id
    }
]

response = requests.post(BASE_URL + SOFTWARE_IMAGE_DISTRIBUTION_URL,
                         json=distribution_info,
                         headers=headers, verify=False)
task_id = response.json()['response']['taskId']

time.sleep(10)

response = requests.get(BASE_URL + TASK_BY_ID_URL.format(task_id=task_id),
                        headers=headers, verify=False)
print(response.json()['response']['data'])

Image activation

Finally, after the image has been distributed, using the API it is possible to ask the device to activate the image. This is also an asynchronous API hence the result needs to be queried from the task API.

SOFTWARE_IMAGE_ACTIVATION_URL = '/dna/intent/api/v1/image/activation/device'
activate_info = [
        {
        'deviceUuid': device_id,
        'imageUuidList': [
            image_id
        ]
    }
]

response = requests.post(BASE_URL + SOFTWARE_IMAGE_ACTIVATION_URL,
                        json=activation_info,
                        headers=headers, verify=False)

task_id = response.json()['response']['taskId']

time.sleep(10)

response = requests.get(BASE_URL + TASK_BY_ID_URL.format(task_id=task_id),
                        headers=headers, verify=False)
print(response.json()['response']['data'])

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

# Disable SSL warnings. Not needed in production environments with valid certificates
import urllib3
urllib3.disable_warnings()

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

# URLs
DEVICES_URL = '/dna/intent/api/v1/network-device'
SOFTWARE_IMAGE_URL='/dna/intent/api/v1/image/importation'
SOFTWARE_IMAGE_IMPORT_URL = '/dna/intent/api/v1/image/importation/source/url'
SOFTWARE_IMAGE_DISTRIBUTION_URL = '/dna/intent/api/v1/image/distribution'
SOFTWARE_IMAGE_ACTIVATION_URL = '/dna/intent/api/v1/image/activation/device'
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'

# 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

# Import software image
def import_image(headers, import_info):
    response = requests.post(BASE_URL + SOFTWARE_IMAGE_IMPORT_URL,
                            json=import_info,
                            headers=headers, verify=False)
    return response.json()['response']

# Get Software Images
def get_software_images(headers, query_params):
    response = requests.get(BASE_URL + SOFTWARE_IMAGE_URL,
                            params=query_params,
                            headers=headers, verify=False)
    return response.json()['response']

# Get devices
def get_devices(headers, query_params):
    response = requests.get(BASE_URL + DEVICES_URL,
                            params=query_params,
                            headers=headers, verify=False)
    return response.json()['response']

# Distribute image
def distribute_image(headers, distribution_info):
    response = requests.post(BASE_URL + SOFTWARE_IMAGE_DISTRIBUTION_URL,
                            json=distribution_info,
                            headers=headers, verify=False)
    return response.json()['response']

# Activate image
def activate_image(headers, activation_info):
    response = requests.post(BASE_URL + SOFTWARE_IMAGE_ACTIVATION_URL,
                            json=activation_info,
                            headers=headers, verify=False)
    return response.json()['response']

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

    # Import image
    import_info = {
        "sourceURL": "http://10.104.49.64/cat3k_caa_universalk9.16.12.03a.SPA.bin"
    }
    response = import_image(headers, import_info)

    time.sleep(30)

    # Get software images
    query_params ={
        'family': 'cat3k'
    }
    response = get_software_images(headers, query_params)
    image_id = response[0]['imageUuid']

    # Get device
    query_params = {
        'hostname': 'CAT3K-03.devnet.local'
    }

    response = get_devices(headers, query_params)
    device_id = response[0]['id']

    distribution_info = [
            {
        'deviceUuid': device_id,
        'imageUuid': image_id
        }
    ]

    response = distribute_image(headers, distribution_info)
    task_id = response['taskId']

    time.sleep(10)

    response = get_task(headers, task_id)
    print(response['data'])

    activate_info = [
            {
            'deviceUuid': device_id,
            'imageUuidList': [
                image_id
            ]
        }
    ]

    response = activate_image(headers, activate_info)
    task_id = response['taskId']

    time.sleep(10)

    response = get_task(headers, task_id)
    print(response['data'])

if __name__ == "__main__":
    main()