Software Image Management Guide

Introduction

Catalyst Center provides APIs to manage the operating system images of the devices that are 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, we recommend that the user already has experience authenticating with Catalyst Center, obtaining device information, and handling asynchronous tasks.

Environment

This guide was developed using:

Software Image Management API

The software image management API includes 5 endpoints for image uploading, distribution and activation.

Get Device Information

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

First, we must authenticate and retrieve a token from the API.

Notes: 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 proceed 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 Catalyst 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 Catalyst 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 obtaining the image, distribute it to network devices. First, set the image as golden using the web interface.

For image distribution, you need the device ID and image ID. Since this is an asynchronous API, query the task API to obtain the result.

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 distributing the image, use the API to instruct the device to activate the image. This is also asynchronous, so query the task API for the result.

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 appears as 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 Catalyst 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()