Network automation starts with a lab, three routers, and a Python script that actually talks to them. No slides, no theory hand-waving — just working code against real (virtual) devices.
This guide walks you through building a NETCONF/RESTCONF automation lab on Cisco CML in under 60 minutes. You'll pull configs, push changes, and deploy across multiple devices programmatically. Everything here runs against IOS-XE with standard YANG models.
Why You Need a Hands-On Lab
Reading API docs doesn't build muscle memory. Network automation requires you to:
- Navigate YANG model namespaces and build XML filters from scratch
- Debug SSH/NETCONF connection issues in real time
- Handle multi-device deployments with threading
- Understand the difference between running and candidate datastores
The only way to get there is repetition against real devices.
What You Need
| Component | Minimum | Recommended |
|---|---|---|
| Cisco CML | 2.5+ (Personal $199/yr) | 2.7+ with API |
| Host Machine | 16 GB RAM, 4 cores | 32 GB, 8 cores |
| Python | 3.9+ | 3.11+ |
| ncclient | Latest via pip | v0.6.15+ |
| IOS-XE Image | CSR1000v or Cat8000v | Cat8000v 17.9+ |
EVE-NG works too if you don't have CML — just make sure NETCONF port 830 is reachable from your host.
Step 1: Build the CML Topology
Start simple. Three routers in a chain with an external connector for host reachability:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cat8000v-1 │────│ Cat8000v-2 │────│ Cat8000v-3 │
│ (IOS-XE) │ │ (IOS-XE) │ │ (IOS-XE) │
│ 10.10.10.1 │ │ 10.10.10.2 │ │ 10.10.10.3 │
└──────┬───────┘ └──────────────┘ └──────────────┘
│
┌──────┴───────┐
│ External │
│ Connector │
│ (Bridge to │
│ host) │
└──────────────┘
In CML:
- Drop three Cat8000v nodes onto the canvas
- Connect them in a chain: R1↔R2, R2↔R3
- Add an External Connector bridged to your host network
- Connect the External Connector to R1's management interface
- Assign management IPs in the 192.168.25.0/24 range
The External Connector gives your Python scripts direct TCP connectivity to the routers on port 830 (NETCONF) and 443 (RESTCONF).
Step 2: Enable NETCONF and RESTCONF on IOS-XE
Console into each router:
configure terminal
! Enable NETCONF over SSH (port 830)
netconf-yang
netconf-yang feature candidate-datastore
! Enable RESTCONF (HTTPS on port 443)
restconf
ip http secure-server
! Create a dedicated automation user
username automation privilege 15 secret AutoPass123!
! Enable SSH for NETCONF transport
line vty 0 4
transport input ssh
login local
end
write memory
Verify NETCONF is running:
show netconf-yang status
! Expected:
! netconf-yang: enabled
! netconf-yang candidate-datastore: enabled
! netconf-yang ssh port: 830
The candidate-datastore feature is important — it lets you stage changes before committing, which is how production automation workflows should work.
Step 3: Install Python Dependencies
python3 -m venv netconf-lab
source netconf-lab/bin/activate
pip install ncclient lxml paramiko requests pyang
| Library | Purpose |
|---|---|
| ncclient | NETCONF client — handles SSH, XML, and RPC |
| lxml | XML parsing for NETCONF filters |
| paramiko | SSH transport (ncclient dependency) |
| requests | HTTP client for RESTCONF |
| pyang | YANG model browser for finding XPaths |
Step 4: Pull the Running Config via NETCONF
First script — connect and dump the config:
#!/usr/bin/env python3
from ncclient import manager
import xml.dom.minidom
DEVICE = {
"host": "192.168.25.11",
"port": 830,
"username": "automation",
"password": "AutoPass123!",
"hostkey_verify": False,
"device_params": {"name": "csr"}
}
def get_running_config():
with manager.connect(**DEVICE) as m:
print(f"Connected to {DEVICE['host']}")
config = m.get_config(source="running")
print(xml.dom.minidom.parseString(config.xml).toprettyxml())
if __name__ == "__main__":
get_running_config()
If you see XML output, you're in business. If not, check:
-
ping 192.168.25.11— basic connectivity -
nc -zv 192.168.25.11 830— port reachable? -
show netconf-yang status— NETCONF actually enabled? - Credentials match?
Step 5: Filtered GET with YANG Models
Pulling the full config is noisy. Use YANG model filters to query specific data:
#!/usr/bin/env python3
from ncclient import manager
import xml.dom.minidom
DEVICE = {
"host": "192.168.25.11",
"port": 830,
"username": "automation",
"password": "AutoPass123!",
"hostkey_verify": False,
"device_params": {"name": "csr"}
}
INTERFACE_FILTER = """
<filter xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name/>
<type/>
<enabled/>
<ietf-ip:ipv4 xmlns:ietf-ip="urn:ietf:params:xml:ns:yang:ietf-ip">
<address>
<ip/>
<netmask/>
</address>
</ietf-ip:ipv4>
</interface>
</interfaces>
</filter>
"""
def get_interfaces():
with manager.connect(**DEVICE) as m:
result = m.get(INTERFACE_FILTER)
print(xml.dom.minidom.parseString(result.xml).toprettyxml())
if __name__ == "__main__":
get_interfaces()
This uses the ietf-interfaces YANG model — one of the most widely implemented standard models across vendors. Understanding YANG namespaces and XPath filtering is the core skill here.
Step 6: Push Configuration via NETCONF
Now the real power — creating a loopback interface programmatically:
#!/usr/bin/env python3
from ncclient import manager
DEVICE = {
"host": "192.168.25.11",
"port": 830,
"username": "automation",
"password": "AutoPass123!",
"hostkey_verify": False,
"device_params": {"name": "csr"}
}
LOOPBACK_CONFIG = """
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>Loopback99</name>
<description>Created by automation script</description>
<type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">
ianaift:softwareLoopback
</type>
<enabled>true</enabled>
<ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
<address>
<ip>99.99.99.1</ip>
<netmask>255.255.255.0</netmask>
</address>
</ipv4>
</interface>
</interfaces>
</config>
"""
def create_loopback():
with manager.connect(**DEVICE) as m:
response = m.edit_config(target="running", config=LOOPBACK_CONFIG)
if response.ok:
print("✅ Loopback99 created — 99.99.99.1/24")
else:
print(f"❌ Failed: {response.errors}")
if __name__ == "__main__":
create_loopback()
Verify on the router:
show ip interface brief | include Loopback99
! Expected: Loopback99 99.99.99.1 YES manual up up
This connect → build XML → edit_config → verify pattern is the foundation of all NETCONF automation.
Step 7: RESTCONF — The HTTP/JSON Alternative
RESTCONF provides the same YANG-model operations over HTTPS with JSON:
#!/usr/bin/env python3
import requests
import json
requests.packages.urllib3.disable_warnings()
BASE_URL = "https://192.168.25.11/restconf"
HEADERS = {
"Accept": "application/yang-data+json",
"Content-Type": "application/yang-data+json"
}
AUTH = ("automation", "AutoPass123!")
def get_interfaces_restconf():
url = f"{BASE_URL}/data/ietf-interfaces:interfaces"
response = requests.get(url, headers=HEADERS, auth=AUTH, verify=False)
if response.status_code == 200:
print(json.dumps(response.json(), indent=2))
else:
print(f"Error: {response.status_code} - {response.text}")
if __name__ == "__main__":
get_interfaces_restconf()
When to use which:
| Protocol | Best For | Transport | Payload |
|---|---|---|---|
| NETCONF | Bulk config, transactions, rollback | SSH :830 | XML |
| RESTCONF | Quick reads, single-resource CRUD | HTTPS :443 | JSON/XML |
Both use YANG models underneath. In practice, use RESTCONF for monitoring and quick queries, NETCONF for config changes that need transactional guarantees.
Step 8: Multi-Device Deployment with Threading
Real automation means managing fleets. Here's parallel deployment across all three routers:
#!/usr/bin/env python3
from ncclient import manager
from concurrent.futures import ThreadPoolExecutor
DEVICES = [
{"host": "192.168.25.11", "loopback_ip": "99.99.99.1"},
{"host": "192.168.25.12", "loopback_ip": "99.99.99.2"},
{"host": "192.168.25.13", "loopback_ip": "99.99.99.3"},
]
COMMON = {
"port": 830,
"username": "automation",
"password": "AutoPass123!",
"hostkey_verify": False,
"device_params": {"name": "csr"}
}
def configure_device(device):
config = f"""
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>Loopback99</name>
<description>Automated deployment</description>
<type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">
ianaift:softwareLoopback
</type>
<enabled>true</enabled>
<ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
<address>
<ip>{device['loopback_ip']}</ip>
<netmask>255.255.255.0</netmask>
</address>
</ipv4>
</interface>
</interfaces>
</config>
"""
try:
with manager.connect(host=device["host"], **COMMON) as m:
response = m.edit_config(target="running", config=config)
status = "✅" if response.ok else "❌"
print(f"{status} {device['host']} — Loopback99 {device['loopback_ip']}")
except Exception as e:
print(f"❌ {device['host']} — {e}")
with ThreadPoolExecutor(max_workers=3) as executor:
executor.map(configure_device, DEVICES)
ThreadPoolExecutor keeps things fast — in production, you'd add error handling, retry logic, and logging.
Where to Go Next
Once you're comfortable with the basics:
-
YANG model exploration —
pyang -f tree ietf-interfaces.yangto browse available models - Candidate datastore workflow — Lock → edit candidate → validate → commit → unlock
-
Ansible with NETCONF —
ansible.netcommon.netconf_configfor declarative config management -
Terraform for IOS-XE — The
CiscoDevNet/terraform-provider-iosxeprovider automates IOS-XE via RESTCONF - pyATS testing — Cisco's Python Automated Test System for state validation
- CI/CD integration — Git-based config deployment with pre-commit validation
NETCONF vs RESTCONF Quick Reference
| Feature | NETCONF | RESTCONF |
|---|---|---|
| Transport | SSH (830) | HTTPS (443) |
| Payload | XML only | JSON or XML |
| Transactions | Full (lock/commit/rollback) | Per-request |
| Streaming | Notifications supported | SSE supported |
| Best for | Bulk config, atomic changes | Monitoring, quick CRUD |
| Python library | ncclient | requests |
FAQ
Do I need programming experience?
Basic Python — variables, loops, functions, pip. The ncclient library handles most NETCONF complexity. Most network engineers get comfortable in 4-6 weeks of focused practice.
How long does lab setup take?
About 30-60 minutes from scratch: topology creation, NETCONF enablement, first script run. Once your base topology is saved, it spins up in under 5 minutes.
CML vs EVE-NG for automation labs?
CML runs official IOS-XE images with full NETCONF/RESTCONF support and has a built-in API for topology management. EVE-NG works but requires more manual setup. CML Personal costs $199/year.
Originally published at FirstPassLab. More deep dives on network automation, protocol internals, and lab builds at firstpasslab.com.
🤖 AI Disclosure: This article was adapted from the original with AI assistance. All technical content, code examples, and recommendations have been reviewed for accuracy.
Top comments (0)