Discovery Guide

Introduction

The Discovery API allows users to discover network devices on the infrastructure using techniques like IP Range, CDP, and more, with options such as SNMP, SSH, and NETCONF.

Using Discovery API, the user can quickly get its devices into Catalyst Center and perform operations that are related to them.

Goal

The goals of this guide are:

  1. Create an area.
  2. Create a building.
  3. Define credentials for the discovery.
  4. Execute a network discovery.
  5. Get the discovery results.
  6. Assign the newly discovered devices to the recently created building.

Discovery workflow

Endpoints and methods used

  • POST /dna/system/api/v1/auth/token
  • POST /dna/intent/api/v1/site
  • POST /dna/intent/api/v1/global-credential/cli
  • POST /dna/intent/api/v1/discovery
  • GET /dna/intent/api/v1/task/{task_id}
  • GET /dna/intent/api/v1/discovery/{discovery_id}/network-device
  • POST /dna/system/api/v1/site/{site_id}/device

Prerequisites

For this guide, we recommend that the developer becomes familiar with authenticating to the Catalyst Center API and the SITE API.

Environment

This guide was developed using:

Discovery API

The Discovery API includes 34 endpoints for credentials management and the discovery process.

Use it to automatically manage network devices under Catalyst Center, eliminating the need to add each device manually.

It supports different discovery options, like IP Range or CDP, plus different set of credentials for the discovery process: SNMP, CLI (SSH/Telnet) and/or NETCONF, amongst others.

For this guide, we execute a discovery but also create a site and assign the discovered devices to the site:

  1. Authenticate against the Catalyst Center API.
  2. Create an area and then a building using Site API.
  3. Create the credentials to be used for the discovery.
  4. Trigger the discovery process.
  5. Get a list of discovered devices.
  6. Assigned the recently discovered devices to the building created in step 2.

Create site area and building

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 time
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']
headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'}

With the token, we can proceed to create the area and building.We need the siteId of the building to assign the devices to that building after discovery.

SITE_URL = '/dna/intent/api/v1/site'
site_area = {
    "type": "area",
    "site": {
        "area": {
            "name": "DNA Center Guide",
            "parentName": "Global"
        }
    }
}

site_headers = headers
site_headers['__runsync'] = 'true'
site_headers['__runsynctimeout'] = '30'
response = requests.post(BASE_URL + SITE_URL,
                         headers=site_headers, json=site_area,
                         verify=False)
area_id = response.json()['siteId']

site_building = {
    "type": "building",
    "site": {
        "building": {
            "name": "DNA Center Guide Building",
            "parentName": "Global/DNA Center Guide",
            "latitude": "37.409424",
            "longitude": "-121.928868"
        }
    }
}

response = requests.post(BASE_URL + SITE_URL,
                         headers=site_headers, json=site_building,
                         verify=False)

Global Credentials API

Execute the discovery process with options like SNMP, SSH, or NETCONF. Credentials can be site-specific, just for discovery, or global. In this case, we use the global credentials API to create SSH credentials for the discovery process.

The global credentials API is part of the Discovery API.

We need the credential id to be used for the discovery process.

CREDENTIALS_URL = '/dna/intent/api/v1/global-credential/cli'
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'
credentials = [
    {
        'description': 'DNA Center Guide Credentials',
        'enablePassword': 'C!scow02',
        'password': 'C!scow02',
        'username': 'devnet',
        'credentialType': 'GLOBAL'
    }
]

response = requests.post(BASE_URL + CREDENTIALS_URL, headers=headers,
                        json=credentials, verify=False)

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

time.sleep(5)

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

Trigger Discovery

Continuing with the progress from the last section, we have the credential_id variable with the information of the credentials we use for this discovery.

We need the discovery_id to query which devices were discovered so we can assign them to a site.

DISCOVERY_URL = '/dna/intent/api/v1/discovery'
discovery = {
    "name": "Discovery-Guide",
    "discoveryType": "Range",
    "ipAddressList": "10.255.3.11-10.255.3.19",
    "protocolOrder": "ssh",
    "timeOut": 5,
    "retryCount": 3,
    "isAutoCdp": False,
    "globalCredentialIdList": [
        credential_id
    ]
}

response = requests.post(BASE_URL + DISCOVERY_URL, headers=headers,
                        json=discovery, 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)
discovery_id = response['progress']

Get Discovered Devices

After executing a discovery, use the discovery_id to query discovered devices and assign them to the building we created earlier.

DISCOVERY_DEVICES_URL = '/dna/intent/api/v1/discovery/{discovery_id}/network-device'
response = requests.get(BASE_URL + DISCOVERY_DEVICES_URL.format(discovery_id=discovery_id),
                        headers=headers, verify=False)
device_ips = []
for device in response.json()['response']:
    device_ips.append({'ip': device['managementIpAddress']})

Site Assigning API

Finally, as part of the Site API, we have an endpoint that allows the user to assign devices to a site.

We would use the previously obtained building_id and device_ips to assign the recently discovered devices to the DNA Center Guide Building we created at the start of the guide.

SITE_DEVICE_URL = '/dna/system/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=devices,
                            verify=False)

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
import sys

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

# Authentication
BASE_URL = 'https://10.10.10.181'
AUTH_URL = '/api/system/v1/auth/token'
USERNAME = 'altus'
PASSWORD = 'x6n7j4sMoojWMp0Wm/@E'

# URLs
CREDENTIALS_URL = '/dna/intent/api/v1/global-credential/cli'
DISCOVERY_URL = '/dna/intent/api/v1/discovery'
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'
SITE_URL = '/dna/intent/api/v1/site'
DISCOVERY_DEVICES_URL = '/dna/intent/api/v1/discovery/{discovery_id}/network-device'
SITE_DEVICE_URL = '/dna/system/api/v1/site/{site_id}/device'

# 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

# Create site
def create_site(headers, site):
    headers['__runsync'] = 'true'
    headers['__runsynctimeout'] = '30'
    response = requests.post(BASE_URL + SITE_URL,
                             headers=headers, json=site,
                             verify=False)
    return response.json()

# 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()


def create_credentials(headers, credentials):
    response = requests.post(BASE_URL + CREDENTIALS_URL, headers=headers,
                            json=credentials, verify=False)
    return response.json()['response']

def create_discovery(headers, discovery):
    response = requests.post(BASE_URL + DISCOVERY_URL, headers=headers,
                            json=discovery, verify=False)
    return response.json()['response']

# Get devices from discovery
def get_discovery_devices(headers, discovery_id):
    response = requests.get(BASE_URL + DISCOVERY_DEVICES_URL.format(discovery_id=discovery_id),
                            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'}

    site_area = {
        "type": "area",
        "site": {
            "area": {
                "name": "DNA Center Guide",
                "parentName": "Global"
            }
        }
    }

    print('Printing area ID...')
    response = create_site(headers, site_area)
    area_id = response['siteId']
    print(area_id)

    site_building = {
        "type": "building",
        "site": {
            "building": {
                "name": "DNA Center Guide Building",
                "parentName": "Global/DNA Center Guide",
                "latitude": "37.409424",
                "longitude": "-121.928868"
            }
        }
    }

    print('\nPrinting building ID...')
    response = create_site(headers, site_building)
    building_id = response['siteId']
    print(building_id)

    credentials = [
        {
            'description': 'DNA Center Guide Credentials',
            'enablePassword': 'C!scow02',
            'password': 'C!scow02',
            'username': 'devnet',
            'credentialType': 'GLOBAL'
        }
    ]
    response = create_credentials(headers, credentials)
    task_id = response['taskId']

    time.sleep(5)

    print('\nPrinting credential id...')
    response = get_task(headers, task_id)
    credential_id = response['progress']
    print(credential_id)

    discovery = {
        "name": "Discovery-Guide",
        "discoveryType": "Range",
        "ipAddressList": "10.255.3.11-10.255.3.19",
        "protocolOrder": "ssh",
        "timeOut": 5,
        "retryCount": 3,
        "isAutoCdp": False,
        "globalCredentialIdList": [
            credential_id
        ]
    }
    response = create_discovery(headers, discovery)
    task_id = response['taskId']

    print('\nWaiting 10 seconds for discovery to be created...')
    time.sleep(10)

    print('\nPrinting discovery id')
    response = get_task(headers, task_id)
    discovery_id = response['progress']
    print(discovery_id)

    print('\nWaiting 30 seconds for discovery to end...')
    time.sleep(30)
    response = get_discovery_devices(headers, discovery_id)
    device_ips = []
    for device in response:
        device_ips.append({'ip': device['managementIpAddress']})

    site_devices = {
        'device': device_ips
    }

    print('\nPrinting device inclusion in site...')
    response = add_devices_site(headers, building_id, site_devices)
    print(response['result']['progress'])


if __name__ == "__main__":
    main()