Forem

FirstPassLab
FirstPassLab

Posted on • Originally published at firstpasslab.com

Build Your First Network Automation Lab: Python + NETCONF on Cisco CML (With Working Code)

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)      │
└──────────────┘
Enter fullscreen mode Exit fullscreen mode

In CML:

  1. Drop three Cat8000v nodes onto the canvas
  2. Connect them in a chain: R1↔R2, R2↔R3
  3. Add an External Connector bridged to your host network
  4. Connect the External Connector to R1's management interface
  5. 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
Enter fullscreen mode Exit fullscreen mode

Verify NETCONF is running:

show netconf-yang status

! Expected:
! netconf-yang: enabled
! netconf-yang candidate-datastore: enabled
! netconf-yang ssh port: 830
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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()
Enter fullscreen mode Exit fullscreen mode

If you see XML output, you're in business. If not, check:

  1. ping 192.168.25.11 — basic connectivity
  2. nc -zv 192.168.25.11 830 — port reachable?
  3. show netconf-yang status — NETCONF actually enabled?
  4. 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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Verify on the router:

show ip interface brief | include Loopback99
! Expected: Loopback99  99.99.99.1  YES manual up  up
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. YANG model explorationpyang -f tree ietf-interfaces.yang to browse available models
  2. Candidate datastore workflow — Lock → edit candidate → validate → commit → unlock
  3. Ansible with NETCONFansible.netcommon.netconf_config for declarative config management
  4. Terraform for IOS-XE — The CiscoDevNet/terraform-provider-iosxe provider automates IOS-XE via RESTCONF
  5. pyATS testing — Cisco's Python Automated Test System for state validation
  6. 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)