Test your knowledge on this topic in the ENAUTO Exam Trainer — 186 questions across 5 interactive modes.
Cisco SD-WAN (vManage) – Deep Dive for ENAUTO v2.0
Exam Relevance
Topic 3.0 Controller-Based Network Automation (30%) — SD-WAN is the WAN-focused controller.
Unlike Catalyst Center (token auth) or Meraki (API key), SD-WAN uses session-based authentication with cookies and requires an XSRF token for write operations.
Using json= sends Content-Type: application/json, which vManage rejects silently (returns 200 but no session).
Exam Trap: Login Returns 200 Even on Failure
Unlike other controllers, vManage returns HTTP 200 even for bad credentials. You must inspect the response body or check for the JSESSIONID cookie to confirm success. An HTML body means login failed.
What Happens on Failure?
Symptom
Meaning
Likely Cause
200 with HTML body
Login failed
Wrong username/password
200 with empty body
Login succeeded
Credentials valid
403 Forbidden on write
Missing XSRF token
Didn’t fetch /client/token
419 Token Expired
Session timed out
JSESSIONID expired (~24h default)
SSL Error
Certificate issue
Missing verify=False in lab
Why requests.Session() Is Critical
# ── WITHOUT Session — cookies are lost between calls ──────────response1 = requests.post(f"{BASE_URL}/j_security_check", data=creds, verify=False)# JSESSIONID cookie in response1 is NOT carried forward:response2 = requests.get(f"{BASE_URL}/dataservice/device", verify=False)# ^^^ This FAILS with 401 — no session cookie sent# ── WITH Session — cookies persist automatically ──────────────session = requests.Session()session.post(f"{BASE_URL}/j_security_check", data=creds, verify=False)# JSESSIONID cookie is stored in session.cookies and sent automatically:response = session.get(f"{BASE_URL}/dataservice/device", verify=False)# ^^^ This WORKS — session cookie is included
Key Difference: requests.Session() vs requests.post()
SD-WAN is the only Cisco controller where you must use requests.Session(). Catalyst Center uses a token header, Meraki uses an API key, ISE uses Basic Auth — all of which work with individual requests. SD-WAN relies on a cookie that must persist across calls.
3 – Authentication with the Catalyst WAN SDK
Exam Topic: 3.2
The SDK handles session management, XSRF tokens, and reconnection automatically.
Installation
pip install catalystwan
Full Working Example
from catalystwan.session import create_manager_session# The SDK handles:# - Session cookie management# - XSRF token fetching# - Automatic re-authentication on session expiry# - SSL verification configsession = create_manager_session( url="sandboxsdwan.cisco.com", username="devnetuser", password="Cisco123!", port=443)print(f"Connected to vManage: {session.server_url}")print(f"Session active: {session.session_type}")# ── Always close the session when done ────────────────────────# This invalidates the JSESSIONID on the serversession.close()
Context Manager Pattern (Recommended)
from catalystwan.session import create_manager_session# Automatic cleanup with context managerwith create_manager_session( url="sandboxsdwan.cisco.com", username="devnetuser", password="Cisco123!") as session: # All API calls here... devices = session.api.devices.get() print(f"Total devices: {len(devices)}")# Session is automatically closed here
SDK vs requests — When to Use What
Aspect
requests + Session
catalystwan SDK
Auth management
Manual (login + XSRF)
Automatic
Session expiry
Handle yourself
Auto-reconnect
XSRF token
Fetch and attach manually
Transparent
Template operations
Raw JSON payloads
Typed Python objects
Exam relevance
High — must know the raw flow
High — must know the SDK exists
Production use
More control, more code
Less code, batteries included
4 – Get Devices and System Status
Exam Topics: 3.2, 4.4
The device endpoint is the foundation for inventory and health monitoring.
Get All Devices (requests)
# ── List all SD-WAN devices ──────────────────────────────────response = session.get( f"{BASE_URL}/dataservice/device", verify=False)response.raise_for_status()devices = response.json()["data"]for device in devices: print(f"{device.get('host-name', 'N/A'):<25} " f"{device.get('system-ip', 'N/A'):<16} " f"{device.get('device-type', 'N/A'):<15} " f"{device.get('reachability', 'N/A')}")
Get Devices by Type
# ── Filter by device type ─────────────────────────────────────# Device types: "vedge", "vsmart", "vbond", "vmanage"response = session.get( f"{BASE_URL}/dataservice/device", params={"device-type": "vedge"}, # WAN edge routers verify=False)vedges = response.json()["data"]# ── Get controllers only ──────────────────────────────────────response = session.get( f"{BASE_URL}/dataservice/device/controllers", verify=False)controllers = response.json()["data"]for ctrl in controllers: print(f"{ctrl.get('deviceType'):<12} " f"{ctrl.get('host-name', 'N/A'):<20} " f"{ctrl.get('system-ip', 'N/A')}")
In SD-WAN, the system-ip is the loopback address that uniquely identifies a device in the overlay network. The deviceId field typically holds the same value. The uuid is used for template attachment and device-specific operations.
Get Device Interface Details
# ── Get interfaces for a specific device ──────────────────────system_ip = "10.10.1.1"response = session.get( f"{BASE_URL}/dataservice/device/interface", params={"deviceId": system_ip}, verify=False)interfaces = response.json()["data"]for intf in interfaces: print(f"{intf.get('ifname', 'N/A'):<20} " f"{intf.get('ip-address', 'N/A'):<16} " f"Admin: {intf.get('if-admin-status', 'N/A'):<6} " f"Oper: {intf.get('if-oper-status', 'N/A')}")
Get Running Configuration
# ── Get running config of a device ────────────────────────────device_id = "C8K-aaaa-bbbb-cccc-dddd" # UUID, not system-ipresponse = session.get( f"{BASE_URL}/dataservice/device/config", params={"deviceId": device_id}, verify=False)running_config = response.json()print(running_config)
5 – Get Devices (SDK)
Exam Topic: 3.2
from catalystwan.session import create_manager_sessionwith create_manager_session( url="sandboxsdwan.cisco.com", username="devnetuser", password="Cisco123!") as session: # ── Get all devices ────────────────────────────────────── devices = session.api.devices.get() for device in devices: print(f"{device.hostname:<25} " f"{device.local_system_ip:<16} " f"{device.reachability}") # ── Filter by device type ──────────────────────────────── controllers = session.api.devices.controllers() edges = session.api.devices.get() # Filter in Python: vedges = [d for d in edges if d.personality == "vedge"]
6 – Feature Templates and Device Templates
Exam Topics: 3.2, 3.3
SD-WAN uses a two-tier template system: Feature Templates define individual settings (VPN, interface, etc.), and Device Templates combine them into a complete device configuration.
The exam may ask about both, but feature template-based is the modern approach.
7 – Attach and Detach Device Templates
Exam Topics: 3.2, 3.1
Attaching a device template is how SD-WAN pushes configuration to devices. This is a multi-step async process.
Step 1: Get Required Variables for a Template
# ── Before attaching, get the variables the template needs ────template_id = "<device-template-id>"response = session.get( f"{BASE_URL}/dataservice/template/device/config/input", params={"templateId": template_id}, verify=False)input_schema = response.json()# This tells you what variables need values per deviceprint("Required variables:")for column in input_schema.get("header", {}).get("columns", []): print(f" {column['property']}: {column.get('title', '')}")
import timedef wait_for_action(session, base_url, action_id, timeout=300, interval=10): """Poll an SD-WAN action until completion.""" elapsed = 0 while elapsed < timeout: response = session.get( f"{base_url}/dataservice/device/action/status/{action_id}", verify=False ) status_data = response.json() summary = status_data.get("summary", {}) status = summary.get("status", "unknown") print(f"Status: {status} " f"(Success: {summary.get('count', {}).get('success', 0)}, " f"Failure: {summary.get('count', {}).get('failure', 0)})") if status in ("done", "Done - Scheduled"): return status_data if summary.get("count", {}).get("failure", 0) > 0: # Check detailed error for device in status_data.get("data", []): if device.get("statusId") == "failure": print(f" FAILED: {device.get('host-name')} " f"— {device.get('activity', ['Unknown error'])}") return status_data time.sleep(interval) elapsed += interval raise TimeoutError(f"Action {action_id} did not complete within {timeout}s")result = wait_for_action(session, BASE_URL, action_id)
Template Attach Is Asynchronous
Like Catalyst Center, SD-WAN write operations (template attach, policy activate, software upgrade) are asynchronous. You submit the request, get an action_id, and must poll /dataservice/device/action/status/{id} for completion. The exam loves testing this pattern under topic 3.6.
When you detach a device template, the device reverts to CLI mode — it keeps its current running config but is no longer managed by templates. This is a significant operational change. The exam may test whether you understand this consequence.
8 – Day-0 Provisioning
Exam Topic: 3.1 – Construct a controller-based network automation solution for Day-0 provisioning
SD-WAN Day-0 provisioning involves bootstrapping WAN edge devices into the SD-WAN fabric through vBond orchestration.
Every SD-WAN edge device needs a minimal bootstrap config to reach vBond:
system
system-ip 10.10.1.1
site-id 100
organization-name "MyOrg"
vbond 198.51.100.1
!
vpn 0
interface GigabitEthernet1
ip address 192.168.1.1/24
tunnel-interface
color biz-internet
!
no shutdown
!
ip route 0.0.0.0/0 192.168.1.254
!
The bootstrap tells the device how to reach vBond. Once it connects, vManage pushes the full configuration.
Step 1: Get Available (Unconfigured) Devices
# ── List devices that are available for provisioning ──────────response = session.get( f"{BASE_URL}/dataservice/device/available", verify=False)available_devices = response.json()["data"]for device in available_devices: print(f"UUID: {device.get('uuid'):<40} " f"Model: {device.get('deviceModel', 'N/A'):<20} " f"Serial: {device.get('board-serial', 'N/A')}")
Step 2: Upload WAN Edge Device List (Serial File)
# ── Upload the WAN edge authorized serial number list ─────────# This is the serial number file from Cisco Smart Account# Format: each line has uuid,serial,model,organization-name,...serial_file_content = """uuid1,SERIAL001,vedge-C8000V,MyOrg,NA,true,falseuuid2,SERIAL002,vedge-C8000V,MyOrg,NA,true,false"""response = session.post( f"{BASE_URL}/dataservice/device/vedge/serialFile", files={"file": ("serial_file.csv", serial_file_content, "text/csv")}, verify=False)response.raise_for_status()print(f"Serial file uploaded: {response.json()}")
Step 3: Attach Device Template for Day-0
# ── Full Day-0 workflow: Upload serial → attach template ──────# 1. Get the device template IDtemplates = session.get( f"{BASE_URL}/dataservice/template/device", verify=False).json()["data"]target_template = next( (t for t in templates if "Branch" in t["templateName"]), None)if not target_template: print("No matching template found!") exit()template_id = target_template["templateId"]# 2. Get the template input variablesinputs = session.get( f"{BASE_URL}/dataservice/template/device/config/input", params={"templateId": template_id}, verify=False).json()# 3. Prepare device-specific variable valuesdevice_variables = { "csv-status": "complete", "csv-deviceId": "C8K-aaaa-bbbb-cccc-dddd", "csv-deviceIP": "10.10.1.1", "csv-host-name": "Branch-cEdge-01", "//system/host-name": "Branch-cEdge-01", "//system/system-ip": "10.10.1.1", "//system/site-id": "100"}# 4. Attach the templateattach_response = session.post( f"{BASE_URL}/dataservice/template/device/config/attachfeature", json={ "deviceTemplateList": [{ "templateId": template_id, "device": [device_variables], "isEdited": False, "isMasterEdited": False }] }, verify=False)action_id = attach_response.json()["id"]print(f"Day-0 provisioning initiated: {action_id}")# 5. Monitor progressresult = wait_for_action(session, BASE_URL, action_id)
SD-WAN Day-0 vs Other Controllers
Aspect
SD-WAN
Catalyst Center
Meraki
Discovery
vBond orchestration + certs
DHCP Option 43 / DNS
Cloud auto-connect
Auth
Certificate-based (PKI)
Certificate + PnP
Serial claim
Config push
Device template attach
PnP template
Network-level settings
Bootstrap
Manual or ZTP serial file
Factory default + DHCP
Power on + claim
Complexity
High — certs, vBond, overlay
Medium — DHCP, templates
Low — claim & go
9 – Centralized Policy Management
Exam Topics: 3.5, 3.2
SD-WAN policies control traffic flow across the overlay. Understanding the policy hierarchy is critical for the exam.
# ── Get app-route stats (SLA measurements per tunnel) ─────────response = session.get( f"{BASE_URL}/dataservice/device/app-route/statistics", params={"deviceId": system_ip}, verify=False)app_stats = response.json()["data"]for stat in app_stats: print(f"Remote: {stat.get('remote-system-ip', 'N/A'):<16} " f"Color: {stat.get('local-color', 'N/A'):<15} " f"Latency: {stat.get('latency', 'N/A'):<8} " f"Loss: {stat.get('loss', 'N/A'):<8} " f"Jitter: {stat.get('jitter', 'N/A')}")
Alarms
# ── Get active alarms ─────────────────────────────────────────response = session.get( f"{BASE_URL}/dataservice/alarms", params={"query": '{"query":{"condition":"AND","rules":[{"field":"active","type":"boolean","value":["true"]}]}}'}, verify=False)alarms = response.json()["data"]for alarm in alarms[:5]: print(f"[{alarm.get('severity', 'N/A')}] " f"{alarm.get('type', 'N/A')}: " f"{alarm.get('message', 'N/A')[:60]} " f"— {alarm.get('host-name', 'N/A')}")
Health Dashboard Script
def sdwan_health_dashboard(session, base_url): """Print a summary health dashboard for the SD-WAN fabric.""" # Get all devices devices = session.get( f"{base_url}/dataservice/device", verify=False ).json()["data"] # Separate by type edges = [d for d in devices if d.get("personality") == "vedge"] controllers = [d for d in devices if d.get("personality") != "vedge"] print("=" * 70) print(f"{'DEVICE':<25} {'TYPE':<12} {'STATUS':<10} {'REACHABLE':<12}") print("=" * 70) for device in sorted(devices, key=lambda d: d.get("host-name", "")): print(f"{device.get('host-name', 'N/A'):<25} " f"{device.get('device-type', 'N/A'):<12} " f"{device.get('status', 'N/A'):<10} " f"{device.get('reachability', 'N/A'):<12}") # Summary reachable = sum(1 for d in devices if d.get("reachability") == "reachable") total = len(devices) print(f"\nSummary: {reachable}/{total} devices reachable") print(f" Controllers: {len(controllers)}") print(f" WAN Edges: {len(edges)}") # Check alarms alarms = session.get( f"{base_url}/dataservice/alarms", verify=False ).json()["data"] critical = [a for a in alarms if a.get("severity") == "Critical"] if critical: print(f"\n {len(critical)} CRITICAL alarm(s):") for a in critical[:5]: print(f" - {a.get('type')}: {a.get('message', '')[:50]}")sdwan_health_dashboard(session, BASE_URL)
11 – Software Image Management
Exam Topic: 4.3 – Construct a controller-based automation solution to manage device software versions
List Available Software Images
# ── Get software images in the vManage repository ─────────────response = session.get( f"{BASE_URL}/dataservice/device/action/software/images", verify=False)images = response.json()["data"]for img in images: print(f"{img.get('versionName', 'N/A'):<30} " f"Type: {img.get('versionType', 'N/A'):<10} " f"Platform: {img.get('platformFamily', 'N/A')}")
Upload a Software Image
# ── Upload a new software image to vManage ────────────────────image_path = "/path/to/c8000v-universalk9.17.09.04a.SPA.bin"with open(image_path, "rb") as f: response = session.post( f"{BASE_URL}/dataservice/device/action/software/package", files={"file": f}, verify=False )response.raise_for_status()print(f"Image uploaded: {response.json()}")
import requestsimport timeimport urllib3urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)BASE_URL = "https://sandboxsdwan.cisco.com"# ── 1. Authenticate ──────────────────────────────────────────session = requests.Session()session.post( f"{BASE_URL}/j_security_check", data={"j_username": "devnetuser", "j_password": "Cisco123!"}, verify=False)xsrf = session.get(f"{BASE_URL}/dataservice/client/token", verify=False).textsession.headers.update({ "X-XSRF-TOKEN": xsrf, "Content-Type": "application/json"})# ── 2. Get device inventory ──────────────────────────────────devices = session.get( f"{BASE_URL}/dataservice/device", verify=False).json()["data"]edges = [d for d in devices if d.get("personality") == "vedge"]print(f"Found {len(edges)} WAN edge devices")# ── 3. Get device templates ──────────────────────────────────templates = session.get( f"{BASE_URL}/dataservice/template/device", verify=False).json()["data"]for t in templates: print(f" {t['templateName']}: {t.get('devicesAttached', 0)} attached")# ── 4. Check BFD tunnel health ───────────────────────────────if edges: bfd = session.get( f"{BASE_URL}/dataservice/device/bfd/sessions", params={"deviceId": edges[0]["system-ip"]}, verify=False ).json()["data"] up_tunnels = sum(1 for b in bfd if b.get("state") == "up") print(f"\nDevice {edges[0]['host-name']}: " f"{up_tunnels}/{len(bfd)} tunnels UP")# ── 5. Check alarms ──────────────────────────────────────────alarms = session.get( f"{BASE_URL}/dataservice/alarms", verify=False).json()["data"]print(f"\nActive alarms: {len(alarms)}")# ── 6. Logout (invalidate session) ───────────────────────────session.get(f"{BASE_URL}/logout", verify=False)print("Session closed")
Gotchas for the Exam
Top SD-WAN Exam Pitfalls
1. Auth uses data={}, NOT json={}
The /j_security_check endpoint expects application/x-www-form-urlencoded. Sending JSON silently fails with a 200 response but no valid session.
2. requests.Session() is mandatory — cookies must persist
SD-WAN is the only Cisco controller where bare requests.get() / requests.post() won’t work. You must use a Session object to carry the JSESSIONID cookie.
3. Write operations need the X-XSRF-TOKEN header
GET requests work with just the session cookie. POST/PUT/DELETE also need the XSRF token from /dataservice/client/token. Forgetting this causes 403 Forbidden.
4. API base path is /dataservice/...
Not /api/v1/... (Meraki), not /dna/intent/... (Catalyst Center). All SD-WAN endpoints start with /dataservice/.
5. Response data is in ["data"], not ["response"]
Catalyst Center wraps results in {"response": [...]}. SD-WAN uses {"data": [...]}.
6. Template operations are asynchronous
Template attach/detach and policy activation return an action ID. You must poll /dataservice/device/action/status/{id} — same concept as Catalyst Center task polling, different endpoint.
7. vipType controls variable binding in feature templates"constant" = fixed, "variableName" = per-device, "ignore" = default. Getting this wrong in template creation is a common exam trap.
8. Login success returns 200 with EMPTY body
Login failure also returns 200 but with an HTML body. Don’t check status_code == 200 to verify login — check the response content or cookie jar.
13 – Exam-Style Scenarios
Scenario 1: Authentication Failure (Topic 3.6)
You run the following code and all subsequent API calls return 401 Unauthorized. What is wrong?
The code uses bare requests.post() and requests.get() instead of a requests.Session(). The JSESSIONID cookie from the login response is not carried to the device request. Fix:
The login uses json= instead of data=. The /j_security_check endpoint requires form-encoded data (application/x-www-form-urlencoded), not JSON. vManage returns 200 even on failed login, so there’s no error to catch. Fix:
You create a VPN feature template where the VPN ID should always be 10, but the hostname should be different per device. Which vipType values should you use?
"vpn-id": { "vipType": "______", # Always VPN 10 "vipValue": 10},"host-name": { "vipType": "______", # Different per device "vipValue": "hostname_var"}
Answer
"vpn-id": { "vipType": "constant", # Fixed — same on all devices "vipValue": 10},"host-name": { "vipType": "variableName", # Per-device — set during attach "vipValue": "hostname_var"}
"constant" — hardcoded, never changes between devices
"variableName" — the template variable name, value provided when attaching
"ignore" — use the device default (omit from config)
Scenario 5: Data Key Confusion (Topic 3.6)
You migrated a working Catalyst Center script to SD-WAN. The auth works, but this line throws KeyError: 'response':
devices = session.get(f"{BASE_URL}/dataservice/device", verify=False)for d in devices.json()["response"]: print(d["hostname"])
Answer
SD-WAN wraps results in {"data": [...]}, not {"response": [...]} like Catalyst Center. Also, the hostname field is "host-name" (hyphenated), not "hostname":
for d in devices.json()["data"]: print(d["host-name"])
Scenario 6: Policy Activation (Topic 3.5)
You've created a centralized traffic data policy with all the correct lists and definitions. You verify it exists in vManage. But traffic is still taking the old path. What step are you missing?
Answer
You need to activate the policy. Creating it in vManage doesn’t push it to vSmarts:
Until a centralized policy is activated, it exists only in vManage’s database. Activation pushes it to vSmarts, which then enforce it across the fabric.
Scenario 7: Day-0 Bootstrap (Topic 3.1)
A new cEdge router powers on with a factory default configuration. It has IP connectivity to the internet. What minimum information must be in its bootstrap config to join the SD-WAN fabric?
Answer
The bootstrap must include:
system-ip — unique overlay address
site-id — identifies the site in the fabric
organization-name — must match vManage org name
vbond address — IP/hostname of the vBond orchestrator
WAN interface with tunnel-interface and color — enables DTLS tunnel
Without any of these, the device cannot authenticate with vBond and join the fabric.
Scenario 8: SDK Authentication (Topic 3.2)
Fill in the blanks to authenticate using the Catalyst WAN SDK:
from catalystwan.session import ____________session = ____________( url="vmanage.example.com", username="admin", password="Cisco123!")
Answer
from catalystwan.session import create_manager_sessionsession = create_manager_session( url="vmanage.example.com", username="admin", password="Cisco123!")
Scenario 9: BFD and App-Aware Routing (Topic 4.4)
Your monitoring script shows BFD sessions between two sites with these metrics:
You have an SLA class requiring loss < 1% and latency < 50ms. Which tunnel will app-aware routing select?
Answer
Tunnel 1 (MPLS) — it meets both SLA requirements (0% loss < 1%, 20ms latency < 50ms). Tunnel 2 fails both criteria (2% > 1% and 80ms > 50ms). Application-aware routing uses BFD measurements to make real-time path decisions based on configured SLA classes.
14 – Quick Reference Cheat Sheet
Authentication Flow
POST /j_security_check
├── Body: j_username=admin&j_password=pass (form-encoded!)
├── Response: JSESSIONID cookie (empty body on success)
└── Then: GET /dataservice/client/token → X-XSRF-TOKEN header
Requires: requests.Session() to persist cookies