Network simulation with Cisco Modeling Labs¶
The safest place to test a configuration change is a network that looks exactly like production but costs nothing to break. This article shows how Cisco Modeling Labs (CML) provides that network, how to drive it from Python so it becomes a stage in a pipeline rather than a manual sandbox, and how a versioned topology file keeps the whole lab reproducible.
What CML is¶
CML is Cisco's network simulation platform. It runs virtual instances of real Cisco operating systems — IOSv, IOS XE (Cat8000v/CSR1000v), IOS XR, NX-OS — alongside Linux hosts, wired together into a topology you define. Because the nodes run the same software images as physical gear, a playbook or Terraform plan that works against the lab will behave the same way against production.
For automation, that is the entire point: CML is where a change is tested against a realistic virtual network before it ever touches production. A mistake in the lab is free and reversible — you wipe and rebuild in seconds; the same mistake on production routers is an outage.
Cisco now ships a no-cost CML-Free tier that runs up to five nodes (plus unmanaged switches and external connectors) without a subscription, which is enough to model a small core and prototype automation against it.
Core concepts¶
A CML deployment is built from four ideas:
| Concept | What it is |
|---|---|
| Lab | One simulation containing nodes and links. You start, stop, and wipe it as a unit. |
| Node | A virtual device created from a node definition — the image and type, e.g. iosxe or iosv. |
| Link | The virtual cabling between two node interfaces. |
| Topology file | A YAML description of the whole lab — nodes, links, and seed configuration — that you can commit to Git and recreate on demand. |
The topology file is what turns a lab from a one-off GUI creation into infrastructure. It lives in the same repository as the automation it validates, so the lab is reviewed, versioned, and rebuildable from scratch.
Driving CML from Python¶
CML exposes a full REST API, and Cisco ships a Python client,
virl2_client, whose ClientLibrary class
wraps it. A script can import a topology, boot the nodes, wait for them to converge, run
tests, and tear the lab down — all without opening the GUI. That is what makes CML a
stage in a pipeline: the same script the pipeline runs, an engineer can run locally.
from virl2_client import ClientLibrary
client = ClientLibrary(
"https://cml.example.com",
"admin",
"password",
ssl_verify=False, # set to a CA bundle path in production
)
# Import a versioned topology and boot it
lab = client.import_lab_from_path("topologies/core.yaml")
lab.start() # activate every node and link
lab.wait_until_lab_converged() # block until the nodes have booted
# ... run your automation and pyATS tests against the running nodes ...
lab.stop()
lab.wipe() # free the resources for the next run
wait_until_lab_converged() is the step that prevents flaky results: it polls the
controller until the nodes have finished booting, so your tests never run against a
half-started device.
One practical gotcha: the client version must be compatible with the controller version.
Pin virl2_client to match your CML release rather than always pulling the newest build,
and the import/start/converge/test/wipe sequence stays stable across upgrades.
A versioned topology file¶
A topology file lists the nodes and the links between them. The most reliable way to create one is to build the topology once in the CML GUI and export it, then commit the resulting YAML. A trimmed export of two IOS XE routers and the link between them looks like this:
lab:
title: core-test
nodes:
- id: n0
label: R1
node_definition: iosxe
configuration: |
hostname R1
- id: n1
label: R2
node_definition: iosxe
links:
- id: l0
n1: n0
n2: n1
Because it is plain YAML in Git, the lab is reviewed like any other change and recreated identically on every pipeline run.
CML in a CI/CD pipeline¶
CML slots naturally into the prevalidate stage of a network pipeline (see Building a GitLab CI/CD pipeline for network automation). In that stage a job:
- Imports the lab from the versioned topology file and starts it.
- Applies the candidate change with the same Ansible or Terraform code that will later run against production.
- Runs pyATS/Genie tests against the virtual devices to confirm the network behaves as intended.
- Wipes the lab to release resources.
If the virtual network passes, the change is trusted enough to deploy; if it fails, the pipeline stops before anything reaches production. You get production-like testing without production risk — the key property when choosing an automation approach is that the lab makes failure cheap.
Snapshots for a known-good baseline¶
Before testing a risky change, take a CML snapshot of the converged lab. If the change breaks routing, restore the snapshot in seconds instead of rebooting and reconfiguring every node by hand. Combined with a version-controlled topology file, this gives you a lab that is both reproducible from scratch and quick to reset mid-experiment — the simulation equivalent of a Git checkpoint.
Key takeaways¶
- CML runs virtual instances of real Cisco OS images, so automation tested against the lab behaves the same way against production.
- A lab is made of nodes (from node definitions) and links; a topology file (YAML) captures the whole thing and belongs in Git alongside your automation.
- Automate it with the
virl2_clientClientLibrary: import →start()→wait_until_lab_converged()→ test →stop()/wipe(). Pin the client to match your controller version. - CML belongs in the prevalidate stage — run the real change against the virtual network, verify with pyATS, then deploy.
- A failure in CML is free and reversible; the same failure in production is an outage. Snapshots give you a known-good baseline to restore in seconds.