Skip to content

Commit

Permalink
Baselined from internal Repository
Browse files Browse the repository at this point in the history
last_commit:1f09fd2607bd6d76a54c2dd24aaf703f7107dec4
  • Loading branch information
GVE Devnet Admin committed Jan 17, 2024
1 parent d197e30 commit 644d65e
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 45 deletions.
4 changes: 2 additions & 2 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
CISCO SAMPLE CODE LICENSE
Version 1.1
Copyright (c) 2020 Cisco and/or its affiliates
Copyright (c) 2024 Cisco and/or its affiliates

These terms govern this Cisco Systems, Inc. ("Cisco"), example or demo
source code and its associated documentation (together, the "Sample
Expand Down Expand Up @@ -28,7 +28,7 @@ CISCO SAMPLE CODE LICENSE
and services are licensed under their own separate terms and you shall
not use the Sample Code in any way that violates or is inconsistent
with those terms (for more information, please visit:
www.cisco.com/go/terms).
<www.cisco.com/go/terms>).

3. OWNERSHIP: Cisco retains sole and exclusive ownership of the Sample
Code, including all intellectual property rights therein, except with
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# DNA Center - VLAN / Port provisioning
# Catalyst Center - VLAN / Port provisioning

[![published](https://static.production.devnetcloud.com/codeexchange/assets/images/devnet-published.svg)](https://developer.cisco.com/codeexchange/github/repo/gve-sw/gve_devnet_dnac_vlan_provisioning)

This repository contains sample code for deploying interface VLAN configuration to Cisco DNA Center via a simplified web interface. This allows someone to create new VLANs & assign them to switch interfaces, without needing to access DNA center or have knowledge of the underlying device configuration.
This repository contains sample code for deploying interface VLAN configuration to Cisco Catalyst Center via a simplified web interface. This allows someone to create new VLANs & assign them to switch interfaces, without needing to access Catalyst Center or have knowledge of the underlying device configuration.

The web application walks through the following workflow:

- Select a DNA center appliance & log in
- Search DNA Center for network switches using a hostname filter
- Select a Catalyst Center appliance & log in
- Search Catalyst Center for network switches using a hostname filter
- Use a web form to create new VLANs, then drag & drop interfaces to each VLAN
- Deploy the provided VLAN/interface configuration to the target device via DNA Center templates
- Monitor DNA center template deployment status & view configuration summary
- Deploy the provided VLAN/interface configuration to the target device via Catalyst Center templates
- Monitor Catalyst Center template deployment status & view configuration summary

## Contacts

- Matt Schmitz (<mattsc@cisco.com>)

## Solution Components

- DNA Center
- Catalyst Center
- Catalyst Switching
- Python / Flask

Expand All @@ -36,7 +36,7 @@ git clone <repo_url>
pip install -r requirements.txt
```

### **Step 3 - Provide DNA Center list**
### **Step 3 - Provide Catalyst Center list**

In order to use this script, a YAML file of DNAC addresses must be provided. This file must be named `dna-servers.yml`. This file also contains the provisioning project & template names.

Expand Down Expand Up @@ -78,9 +78,9 @@ A few additional items may be configured via environment variables. Environment

There are two authentication modes for this web app, single or multiple.

Single authentication mode is the default and will be used if no other configuration is specified. In this mode, the web app user provides their DNA-C login credentials - and that user account is used for all subsequent API calls to DNA Center.
Single authentication mode is the default and will be used if no other configuration is specified. In this mode, the web app user provides their DNA-C login credentials - and that user account is used for all subsequent API calls to Catalyst Center.

Multiple authentication mode is intended to be used in situations where the end user does not have API write access to DNA Center. In this mode, the user still logs into the web app with their own DNA-C user account. However, these credentials are only used to authenticate to DNA-C & ensure the user has access. All subsequent API calls are performed by a separate DNA-C service account with write access to the API. This ensures that the end user can accomplish the provisioning without their DNA Center account requiring API write privileges.
Multiple authentication mode is intended to be used in situations where the end user does not have API write access to Catalyst Center. In this mode, the user still logs into the web app with their own DNA-C user account. However, these credentials are only used to authenticate to DNA-C & ensure the user has access. All subsequent API calls are performed by a separate DNA-C service account with write access to the API. This ensures that the end user can accomplish the provisioning without their Catalyst Center account requiring API write privileges.

Multiple authentication can be configured by setting the following environment variables:

Expand Down Expand Up @@ -114,7 +114,7 @@ Alternatively, a docker-compose.yml file has been included as well.

# Related Sandbox

- [Cisco DNA Center Lab](https://devnetsandbox.cisco.com/RM/Diagram/Index/b8d7aa34-aa8f-4bf2-9c42-302aaa2daafb?diagramType=Topology)
- [Cisco Catalyst Center Lab](https://devnetsandbox.cisco.com/RM/Diagram/Index/b8d7aa34-aa8f-4bf2-9c42-302aaa2daafb?diagramType=Topology)

# Screenshots

Expand Down
57 changes: 34 additions & 23 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" Copyright (c) 2023 Cisco and/or its affiliates.
""" Copyright (c) 2024 Cisco and/or its affiliates.
This software is licensed to you under the terms of the Cisco Sample
Code License, Version 1.1 (the "License"). You may obtain a copy of the
License at
Expand All @@ -13,24 +13,26 @@
"""

# Import Section
from flask import Flask, render_template, request, redirect, flash, session
import sys
import os
import re
import secrets
import string
import sys
from datetime import datetime
import secrets
import yaml
from jinja2 import Environment, FileSystemLoader
from time import sleep
import re

import yaml
from dnacentersdk import api
from dnacentersdk.exceptions import ApiError
from flask_session import Session
from dotenv import load_dotenv
import os
from flask import Flask, redirect, render_template, request, session
from jinja2 import Environment, FileSystemLoader

from flask_session import Session

# Load environment variables
load_dotenv()
CUSTOMER_NAME = os.getenv("CUSTOMER_NAME", "Cisco DNA Center")
CUSTOMER_NAME = os.getenv("CUSTOMER_NAME", "Cisco Catalyst Center")
# App Mode enables single-credential or multiple-credential authentication to DNA Center.
# SINGLEAUTH mode means that the credentials used to log into the web app are also used to push templates to DNA Center.
# MULTIAUTH mode allows users with DNAC read-only API access to use this app by providing a separate API write credential
Expand Down Expand Up @@ -92,6 +94,12 @@ def login():
Collect user credentials & check ability to log into DNAC.
"""
# Set up session data
session["deploymentError"] = None
session["deploymentStatus"] = None
session["template_payload"] = None
session["target_device"] = None

# On GET, render login page:
if request.method == "GET":
# If already authenticated, redirect
Expand Down Expand Up @@ -137,15 +145,14 @@ def login():
error=errormessage,
customer_name=CUSTOMER_NAME,
)

if dnac.access_token != "":
# As long we verified we could authenticate once, mark session as authenticated
# and send user to VLAN provisioning page
session["auth"] = True
return redirect("/select-device")
else:
# If for some reason we don't have an authentication token, return login page & error
errormessage = f"Invalid DNAC access token"
errormessage = "Invalid DNAC access token"
session["auth"] = False
return render_template(
"login.html",
Expand Down Expand Up @@ -218,21 +225,18 @@ def vlan_provision():
# If form submitted, generate template & upload to DNAC
# then push for provisioning
if request.method == "POST":
# Generate unique template ID based on session cookie.
# This allows multiple people to use app without overwriting the same template
session["template_uid"] = request.cookies.get("session")[-18:]
# Generate / Upload / Deploy template via DNAC
generateTemplatePayload(request.json)
uploadTemplate(
session["template_payload"], devices[session.get("target_device")]
)
deployTemplate(devices[session.get("target_device")])
session["deploymentStatus"] = None
deployTemplate(devices[session.get("target_device")])
# Return status page after deployment is started
return redirect("/status")

# On first page load, query device interfaces to populate drag & drop
if not "interfaces" in devices[session.get("target_device")].keys():
if "interfaces" not in devices[session.get("target_device")].keys():
getDeviceInterfaces(session.get("target_device"))

return render_template(
Expand Down Expand Up @@ -313,7 +317,7 @@ def getDNACDevices(filter: str) -> None:
device_list[mgtIP]["family"] = device["family"]
device_list[mgtIP]["series"] = device["series"]
timestamp = datetime.fromtimestamp(device["lastUpdateTime"] / 1000)
device_list[mgtIP]["lastupdate"] = timestamp.strftime(f"%b %d %Y, %I:%M%p")
device_list[mgtIP]["lastupdate"] = timestamp.strftime("%b %d %Y, %I:%M%p")

# Query device location
for device in device_list:
Expand Down Expand Up @@ -341,7 +345,7 @@ def getDeviceInterfaces(device_ip: str) -> None:

for interface in interfaces["response"]:
# Only select GigabitEthernet interfaces on the first module.
# Should skip management interface & NIM slots
# Should skip management interface, NIM slots, and AppGig interface
if re.match("^GigabitEthernet\d\/0\/\d", interface["portName"]):
# Store interface name to UUID mapping
session["dnac_devices"][device_ip]["interfaces"][
Expand Down Expand Up @@ -544,10 +548,17 @@ def deployTemplate(device_info: dict) -> None:
session["deploymentStatus"] = "inprogress"
session["deploymentError"] = None
# Deploy template
deploy_template = dnac.configuration_templates.deploy_template(
templateId=session["templateID"],
targetInfo=target_devices,
)
try:
deploy_template = dnac.configuration_templates.deploy_template(
templateId=session["templateID"],
targetInfo=target_devices,
)
except ApiError as e:
app.logger.error("Error deploying template: ")
app.logger.error(e)
session["deploymentStatus"] = "fail"
session["deploymentError"] = str(e)
return
# Grab deployment UUID
session["deploy_id"] = str(deploy_template.deploymentId).split(":")[-1].strip()
# If any errors are generated, they are included in the deploymentId field
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
image: ghcr.io/gve-sw/gve_devnet_dnac_vlan_provisioning:latest
container_name: gve_devnet_dnac_vlan_provisioning
volumes:
- /config_templates:/app/config_templates/
- ./config_templates:/app/config_templates/
- ./dna-servers.yaml:/app/dna-servers.yaml
ports:
- "5000:5000"
Expand Down
3 changes: 3 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
APP_MODE=
DNAC_USER=
DNAC_PASS=
6 changes: 3 additions & 3 deletions example_dna-servers.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
servers:
server-name-01:
alias: DNA 01
alias: Catalyst Center 01
address: 10.10.10.10
server-name-02:
alias: DNA 02
alias: Catalyst Center 02
address: 10.20.20.20
templates:
project: DNAC_Project
project: Catalyst_Center_Project
template: api_port_vlan_config
4 changes: 2 additions & 2 deletions templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<hr>
<div class="flex flex-center" style="margin-bottom:20px;">
<p>
<h2>DNAC VLAN Provisioning</h2>
<h2>VLAN Provisioning</h2>
</p>
</div>

Expand Down Expand Up @@ -69,7 +69,7 @@ <h2>DNAC VLAN Provisioning</h2>
<option value="{{server}}">{{ dnac.servers[server].alias }}</option>
{% endfor %}
</select>
<label for="input-type-select">DNA Center Server</label>
<label for="input-type-select">Catalyst Center Server</label>
</div>
</div>

Expand Down
4 changes: 2 additions & 2 deletions templates/masterPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<title>DNAC VLAN Provisioning</title>
<title>Catalyst Center VLAN Provisioning</title>


<link rel="stylesheet" href="{{ url_for('static', filename='css/cui-standard.css') }}">
Expand All @@ -29,7 +29,7 @@
<a class="header-bar__logo header__logo" href="https://gve-devnet.cisco.com/" target="_blank">
<span id="header-logo" class="icon-cisco"></span>
</a>
<h1 class="header__title">DNAC VLAN Provisioning</h1>
<h1 class="header__title">Catalyst Center VLAN Provisioning</h1>
</div>
<!--Center area (middle)-->
<div class="header-panel header-panel--center">
Expand Down
11 changes: 10 additions & 1 deletion templates/select-device.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ <h2 class="subtitle">Step 1: Filter Devices</h2>
<div class="section">
<div class="panel panel--loose panel--raised base-margin-bottom">
<h2 class="subtitle">Step 2: Select Device</h2>
<p>Select target device below for provisioning</p>
<p>Select target device below for provisioning.</p>
<p><b>Note:</b> Unreachable devices may not be selected.</p>

<div class="section">
<form action="/select-device" method="POST">
Expand All @@ -94,8 +95,14 @@ <h2 class="subtitle">Step 2: Select Device</h2>
<tr>
<td>
<label class="radio">
{% if device_list[device].reachability == "Reachable" %}
<input type="radio" name="target-device" value="{{ device }}">
<span class="radio__input"></span>
{% else %}
<input type="radio" name="target-device" value="{{ device }}" disabled>
<span class="radio__input disabled"></span>
{% endif %}

</label>
</td>
<td>
Expand All @@ -106,6 +113,8 @@ <h2 class="subtitle">Step 2: Select Device</h2>
<td class="text-center">
{% if device_list[device].reachability == "Reachable" %}
<span class="icon-check text-success"></span>
{% else %}
<span class="icon-exit text-danger"></span>
{% endif %}
</td>
<td class="text-center">{{ device }}</td>
Expand Down

0 comments on commit 644d65e

Please sign in to comment.