Software Defined Access Guide

Introduction

The Software Defined Access (SD-Access) API lets developers manage SD-Access networks via Catalyst Center. It enables creating and managing border, edge, and control plane devices, assigning ports for user devices and access points, and deploying an SD-Access fabric.

Goal

The goals of this guide are:

  1. Create an SD-Access fabric.
  2. Assign a site to the SD-Access Fabric.
  3. Create an authentication profile.
  4. Create a virtual network.
  5. Add an IP pool to the virtual network.
  6. Define a device as a control plane device.
  7. Define a device as an edge device.
  8. Define a device as a border device.
  9. Assign a port as a user device port.
  10. Assign a port as an access point port.

SD-Access workflow

Endpoints and methods used

  • POST /dna/system/api/v1/auth/token
  • POST /dna/intent/api/v1/business/sda/fabric
  • POST /dna/intent/api/v1/business/sda/fabric-site
  • POST /dna/intent/api/v1/business/sda/authentication-profile
  • POST /dna/intent/api/v1/business/sda/virtual-network
  • POST /dna/intent/api/v1/business/sda/virtualnetwork/ippool
  • POST /dna/intent/api/v1/business/sda/control-plane-device
  • POST /dna/intent/api/v1/business/sda/border-device
  • POST /dna/intent/api/v1/business/sda/edge-device
  • POST /dna/intent/api/v1/business/sda/hostonboarding/user-device
  • POST /dna/intent/api/v1/business/sda/hostonboarding/access-point

Prerequisites

For this guide, we recommend that the developer becomes familiar with authenticating to the Catalyst Center API, managing sites, devices, and global IP pools.

Environment

This guide was developed using:

Authentication

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

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

Software Defined Access API

The Software Defined Access API is a complete API with 32 endpoints that allows the developer to manage the SD-Access Fabric using Cisco Catalyst Center.

SD-Access Fabric Creation

Once the developer has access to the token, it is possible to proceed with the Fabric creation:

SDA_FABRIC_URL = '/dna/intent/api/v1/business/sda/fabric'
sda_fabric_info = {
    "fabricName": "DNAC_Guide_Fabric"
}
response = requests.post(BASE_URL + SDA_FABRIC_URL, headers=headers,
                            json=sda_fabric_info, verify=False)

SD-Access Fabric - Site assignment

With the SD-Access Fabric created, the next step is to assign a site to the SD-Access fabric. You can reference the Sites guide to check how to create and retrieve a site.

SITE_FABRIC_URL = '/dna/intent/api/v1/business/sda/fabric-site'
site_fabric_info = {
    "fabricName": "DNAC_Guide_Fabric",
    "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building"
}
response = requests.post(BASE_URL + SITE_FABRIC_URL, headers=headers,
                            json=site_fabric_info, verify=False)

SD-Access Fabric - Authentication Profile

After creating the fabric and selecting the site, assign an authentication profile for the devices in the fabric. In this guide we use a No Authentication profile.

SDA_AUTHENTICATION_PROFILE_URL = '/dna/intent/api/v1/business/sda/authentication-profile'
authentication_profile = [
    {
        "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building",
        "authenticateTemplateName": "No Authentication"
    }
]
response = requests.post(BASE_URL + SDA_AUTHENTICATION_PROFILE_URL, headers=headers,
                            json=authentication_profile, verify=False)

SD-Access Fabric - Virtual Network

As part of the fabric creation, create a Virtual Network where you will later assign IP Address pools. Tie the virtual networks to the site.

VIRTUAL_NETWORK_URL = '/dna/intent/api/v1/business/sda/virtual-network'
virtual_network_info = {
    "virtualNetworkName": "INFRA_VN",
    "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building"
}
response = requests.post(BASE_URL + VIRTUAL_NETWORK_URL, headers=headers,
                         json=virtual_network, verify=False)

SD-Access Fabric - IP Pools

Once you create the virtual network, assign IP Pools to it. Check the Global IP pools for reference on creating IP Pools. For this guide, we create an IP pool for data and another one for voice traffic.

VIRTUAL_NETWORK_IPPOOL_URL = '/dna/intent/api/v1/business/sda/virtualnetwork/ippool'
ip_pool_info = [
    {
        "virtualNetworkName": "INFRA_VN",
        "ipPoolName": "data_ip_pool1",
        "trafficType": "DATA",
        "authenticationPolicyName": "No Authentication",
        "scalableGroupName": "",
        "isL2FloodingEnabled": True,
        "isThisCriticalPool": True,
        "poolType": "AP"
    }
]
response = requests.post(BASE_URL + VIRTUAL_NETWORK_IPPOOL_URL, headers=headers,
                         json=ip_pool, verify=False)

ip_pool_info = [
    {
        "virtualNetworkName": "INFRA_VN",
        "ipPoolName": "voice_ip_pool1",
        "trafficType": "VOICE",
        "authenticationPolicyName": "No Authentication",
        "scalableGroupName": "",
        "isL2FloodingEnabled": True,
        "isThisCriticalPool": True,
        "poolType": "Extended"
    }
]

response = requests.post(BASE_URL + VIRTUAL_NETWORK_IPPOOL_URL, headers=headers,
                         json=ip_pool, verify=False)

SD-Access Fabric - Control Plane, Border, and Edge devices

With the virtual network defined and the IP pools that are assigned, we can proceed to define the different types of devices on the fabric. Check the Devices Guide for reference on how to get the devices information.

First we define the control plane device:

FABRIC_CONTROL_PLANE_URL = '/dna/intent/api/v1/business/sda/control-plane-device'
device_info = [
    {
        "deviceManagementIpAddress": "10.195.192.96",
        "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building"
    }
]
response = requests.post(BASE_URL + FABRIC_CONTROL_PLANE_URL, headers=headers,
                         json=device_info, verify=False)

Next we create a border device.

FABRIC_BORDER_URL = '/dna/intent/api/v1/business/sda/border-device'
device_info = [
    {
        "deviceManagementIpAddress": "10.195.192.95",
        "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building",
        "externalDomainRoutingProtocolName": "BGP",
        "externalConnectivityIpPoolName": "sda_1",
        "internalAutonomouSystemNumber": "65002",
        "borderSessionType": "EXTERNAL",
        "connectedToInternet": False,
        "externalConnectivitySettings": [
            {
                "interfaceName": "FortyGigabitEthernet1/1/1",
                "externalAutonomouSystemNumber": "65003",
                "l3Handoff": [
                    {
                        "virtualNetwork": {
                            "virtualNetworkName": "INFRA_VN"
                        }
                    }
                ]
            }
        ]
    }
]
response = requests.post(BASE_URL + FABRIC_BORDER_URL, headers=headers,
                         json=device_info, verify=False)

Finally, we assign the edge role to a device:

FABRIC_EDGE_URL = '/dna/intent/api/v1/business/sda/edge-device'
device_info = [
    {
        "deviceManagementIpAddress": "10.195.192.95",
        "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building"
    }
]

response = requests.post(BASE_URL + FABRIC_EDGE_URL, headers=headers,
                         json=device_info, verify=False)

SDA Fabric - Port assignment

With the Fabric created, the virtual network and IP pools defined, and the devices' roles that are assigned we can proceed to assign ports for user device or access points on the edge device.

The user device port assignment appears as below:

SDA_PORT_ASSIGNMENT_URL = '/dna/intent/api/v1/business/sda/hostonboarding/user-device'
port_assignment_info = [
    {
        "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building",
        "deviceManagementIpAddress": "10.195.192.95",
        "interfaceName": "GigabitEthernet1/0/1",
        "dataIpAddressPoolName": "data_ip_pool1",
        "voiceIpAddressPoolName": "voice_ip_pool1",
        "scalableGroupName": "testGroupName",
        "authenticateTemplateName": "No Authentication"
    }
]
response = requests.post(BASE_URL + SDA_PORT_ASSIGNMENT_URL, headers=headers,
                            json=port_assignment, verify=False)

The Access Point port assigment appears as below:

SDA_AP_ASSIGNMENT_URL = '/dna/intent/api/v1/business/sda/hostonboarding/access-point'
port_assignment_info = [
    {
        "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building",
        "deviceManagementIpAddress": "10.195.192.95",
        "interfaceName": "GigabitEthernet1/0/1",
        "addressPoolName": "data_ip_pool1",
        "authenticateTemplateName": "No Authentication"
    }
]
response = requests.post(BASE_URL + SDA_PORT_ASSIGNMENT_URL, headers=headers,
                         json=port_assignment, 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://<IP Address>'
AUTH_URL = '/dna/system/api/v1/auth/token'
USERNAME = '<USERNAME>'
PASSWORD = '<PASSWORD>'

# URLs
SDA_FABRIC_URL = '/dna/intent/api/v1/business/sda/fabric'
SITE_URL = '/dna/intent/api/v1/site'
SITE_FABRIC_URL = '/dna/intent/api/v1/business/sda/fabric-site'
FABRIC_CONTROL_PLANE_URL = '/dna/intent/api/v1/business/sda/control-plane-device'
FABRIC_BORDER_URL = '/dna/intent/api/v1/business/sda/border-device'
FABRIC_EDGE_URL = '/dna/intent/api/v1/business/sda/edge-device'
SDA_AUTHENTICATION_PROFILE_URL = '/dna/intent/api/v1/business/sda/authentication-profile'
VIRTUAL_NETWORK_URL = '/dna/intent/api/v1/business/sda/virtual-network'
VIRTUAL_NETWORK_IPPOOL_URL = '/dna/intent/api/v1/business/sda/virtualnetwork/ippool'
SDA_PORT_ASSIGNMENT_URL = '/dna/intent/api/v1/business/sda/hostonboarding/user-device'
SDA_AP_ASSIGNMENT_URL = '/dna/intent/api/v1/business/sda/hostonboarding/access-point'

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 devices
def get_sda_fabric(headers, query_string_params):
    response = requests.get(BASE_URL + SDA_FABRIC_URL,
                            headers = headers, params = query_string_params,
                            verify=False)
    return response.json()

# Create sda fabric
def create_sda_fabric(headers, sda_fabric_info):
    response = requests.post(BASE_URL + SDA_FABRIC_URL, headers=headers,
                             json=sda_fabric_info, verify=False)
    return response.json()

# Get list of sites
def get_sites(headers, site_params):
    response = requests.get(BASE_URL + SITE_URL, headers=headers,
                            params=site_params, verify=False)
    return response.json()['response']

# Add site to SDA fabric
def add_site_to_sda_fabric(headers, site_fabric_info):
    response = requests.post(BASE_URL + SITE_FABRIC_URL, headers=headers,
                             json=site_fabric_info, verify=False)
    return response.json()

# Add Control Plane device
def add_control_plane_device(headers, device_info):
    response = requests.post(BASE_URL + FABRIC_CONTROL_PLANE_URL, headers=headers,
                             json=device_info, verify=False)
    return response.json()

# Add Border device
def add_border_device(headers, device_info):
    response = requests.post(BASE_URL + FABRIC_BORDER_URL, headers=headers,
                             json=device_info, verify=False)
    return response.json()

# Add Edge device
def add_edge_device(headers, device_info):
    response = requests.post(BASE_URL + FABRIC_EDGE_URL, headers=headers,
                             json=device_info, verify=False)
    return response.json()

# Add default authentication profile in SDA Fabric
def add_authentication_profile(headers, authentication_profile):
    response = requests.post(BASE_URL + SDA_AUTHENTICATION_PROFILE_URL, headers=headers,
                             json=authentication_profile, verify=False)
    return response.json()

# Add default authentication profile in SDA Fabric
def add_virtual_network(headers, virtual_network):
    response = requests.post(BASE_URL + VIRTUAL_NETWORK_URL, headers=headers,
                             json=virtual_network, verify=False)
    return response.json()

# Add Virtual Network IP Pool
def add_virtual_network_ip_pool(headers, ip_pool):
    response = requests.post(BASE_URL + VIRTUAL_NETWORK_IPPOOL_URL, headers=headers,
                             json=ip_pool, verify=False)
    return response.json()

# Add user device port assignment
def add_user_device_port_assignment(headers, port_assignment):
    response = requests.post(BASE_URL + SDA_PORT_ASSIGNMENT_URL, headers=headers,
                             json=port_assignment, verify=False)
    return response.json()

# Add port assignment
def add_ap_port_assignment(headers, port_assignment):
    response = requests.post(BASE_URL + SDA_PORT_ASSIGNMENT_URL, headers=headers,
                             json=port_assignment, verify=False)
    return response.json()



def main():
    # obtain the Catalyst Center Auth Token
    token = get_dnac_jwt_token()
    headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'}

    # Create SDA Fabric
    sda_fabric_info = {
        "fabricName": "DNAC_Guide_Fabric"
    }
    response = create_sda_fabric(headers, sda_fabric_info)

    site_fabric_info = {
        "fabricName": "DNAC_Guide_Fabric",
        "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building"
    }

    response = add_site_to_sda_fabric(headers, site_fabric_info)

    authentication_profile = [
        {
            "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building",
            "authenticateTemplateName": "No Authentication"
        }
    ]

    response = add_authentication_profile(headers, authentication_profile)

    virtual_network_info = {
        "virtualNetworkName": "INFRA_VN",
        "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building"
    }

    response = add_virtual_network(headers, virtual_network_info)

    ip_pool_info = [
        {
            "virtualNetworkName": "INFRA_VN",
            "ipPoolName": "data_ip_pool1",
            "trafficType": "DATA",
            "authenticationPolicyName": "No Authentication",
            "scalableGroupName": "",
            "isL2FloodingEnabled": True,
            "isThisCriticalPool": True,
            "poolType": "AP"
        }
    ]

    response = add_virtual_network_ip_pool(headers, ip_pool_info)

    ip_pool_info = [
        {
            "virtualNetworkName": "INFRA_VN",
            "ipPoolName": "voice_ip_pool1",
            "trafficType": "VOICE",
            "authenticationPolicyName": "No Authentication",
            "scalableGroupName": "",
            "isL2FloodingEnabled": True,
            "isThisCriticalPool": True,
            "poolType": "Extended"
        }
    ]

    response = add_virtual_network_ip_pool(headers, ip_pool_info)

    device_info = [
        {
            "deviceManagementIpAddress": "10.195.192.96",
            "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building"
        }
    ]

    response = add_control_plane_device(headers, device_info)
    print(response)

    device_info = [
        {
            "deviceManagementIpAddress": "10.195.192.95",
            "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building",
            "externalDomainRoutingProtocolName": "BGP",
            "externalConnectivityIpPoolName": "sda_1",
            "internalAutonomouSystemNumber": "65002",
            "borderSessionType": "EXTERNAL",
            "connectedToInternet": False,
            "externalConnectivitySettings": [
                {
                    "interfaceName": "FortyGigabitEthernet1/1/1",
                    "externalAutonomouSystemNumber": "65003",
                    "l3Handoff": [
                        {
                            "virtualNetwork": {
                                "virtualNetworkName": "INFRA_VN"
                            }
                        }
                    ]
                }
            ]
        }
    ]
    response = add_border_device(headers, device_info)

    device_info = [
        {
            "deviceManagementIpAddress": "10.195.192.95",
            "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building"
        }
    ]

    response = add_edge_device(headers, device_info)

    port_assignment_info = [
        {
            "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building",
            "deviceManagementIpAddress": "10.195.192.95",
            "interfaceName": "GigabitEthernet1/0/1",
            "dataIpAddressPoolName": "data_ip_pool1",
            "voiceIpAddressPoolName": "voice_ip_pool1",
            "scalableGroupName": "testGroupName",
            "authenticateTemplateName": "No Authentication"
        }
    ]

    response = add_user_device_port_assignment(headers, port_assignment_info)

    port_assignment_info = [
        {
            "siteNameHierarchy": "Global/DNA_Center_Guide/DNA_Center_Guide_Building",
            "deviceManagementIpAddress": "10.195.192.95",
            "interfaceName": "GigabitEthernet1/0/1",
            "addressPoolName": "data_ip_pool1",
            "authenticateTemplateName": "No Authentication"
        }
    ]
    response = add_ap_port_assignment(headers, port_assignment_info)

if __name__ == "__main__":
    main()