← All projects

Network automation: from intent to devices

CPE Lyon, WAN DevNet course, 2026 In progress

A 3-site Cisco network described as data (YAML, then NetBox), validated in Python, rendered with Jinja2 and deployed with Netmiko, NAPALM and Nornir.

Intent means describing what the network should be as data. The code then checks it, turns it into Cisco configurations and deploys them to the devices.

Course subjects, lab and code skeletons are provided by the teacher; I write the data, functions and templates. Graded code is not published.

At a glance

  • 3 sitesLyon, Marseille, Paris
  • 10 devicesin the Nornir inventory
  • 9 testspytest, all passing
  • ~12 sOSPF deployed on 5 routers in parallel

The network

Network of the automation projectTwo core routers, CORE-1 and CORE-2, provided by the teacher, run OSPF area 0 with three sites. Lyon and Marseille each have two routers with VRRP and a switch. Paris has one router and a switch. Each site uses VLANs 10 and 20 over 802.1Q trunks and an admin VLAN 99.CORE-1CORE-2provided by the teacherOSPF area 0Lyon, head officeR1-LYONR2-LYONSwitchVLAN 10/20802.1QVRRPadmin VLAN 99MarseilleR1-MARSR2-MARSSwitchVLAN 10/20802.1QVRRPadmin VLAN 99Paris, branchR1-PARSSwitchVLAN 10/20802.1Qadmin VLAN 99Network of the automation projectTwo core routers, CORE-1 and CORE-2, provided by the teacher, run OSPF area 0 with three sites. Lyon and Marseille each have two routers with VRRP and a switch. Paris has one router and a switch. Each site uses VLANs 10 and 20 over 802.1Q trunks and an admin VLAN 99.CORE-1CORE-2provided by the teacherOSPF area 0Lyon, head officeR1-LYONR2-LYONSwitchVLAN 10/20802.1QVRRPadmin VLAN 99MarseilleR1-MARSR2-MARSSwitchVLAN 10/20802.1QVRRPadmin VLAN 99Paris, branchR1-PARSSwitchVLAN 10/20802.1Qadmin VLAN 99

Emulated in GNS3, individual work. VRRP on two routers at Lyon and Marseille, one router at Paris.

The chain

  1. 1IntentYAML, then NetBox
  2. 2Validateyamllint, check_site, pytest
  3. 3RenderJinja2, 8 configs
  4. 4DeployNetmiko, NAPALM, Nornir
  5. 5DevicesGNS3

The project follows the Network Automation Forum (NAF) framework: intent, executor, then observability.

Progress

  1. TP-01 Describe the network

    Lyon configured by hand, the 3 sites described in YAML.

  2. TP-02 Validate and generate

    check_site() with 4 checks, Jinja2 templates, 8 configs.

  3. TP-03 Netmiko and NAPALMPartial

    Scripted deployment, dry-run diff, then commit. NAPALM rollback test not verified.

  4. TP-04 Scale with NornirPartial

    10-device inventory, OSPF on 5 routers in about 12 s. OSPF failover test not verified.

  5. TP-05 NetBox as source of truth

    Same data, same configs, deployed from NetBox.

  6. TP-06 Drift, orchestration, CINext

    Subject not published yet.

  7. TP-07 Final testNext

    The whole chain. Not started.

A change made in NetBox, not in a file

  1. 1VRRP priority of R2-LYON set to 120 in the NetBox interface
  2. 2Dry-run: the diff shows the change
  3. 3Deployment to the devices
  4. 4R2-LYON becomes Master

YAML and NetBox give the same dictionary and identical configs (diff -r): only the source of truth changes, not the templates.

Extracts

Intent: a router in data/pars.yaml
  - hostname: R1-PARS
    platform: ios
    model: c7200
    loopback: 10.255.0.31/32
    lan_interface: GigabitEthernet0/0
    wan:
      - interface: Serial1/0
        ip: 10.0.0.22/30
        peer: CORE-1
      - interface: Serial1/1
        ip: 10.0.0.26/30
        peer: CORE-2
    ips:
      10: 172.16.50.254/24
      20: 172.16.60.254/24
Generated: config/R1-PARS.cfg (extract, one router so no VRRP line)
hostname R1-PARS
!
interface GigabitEthernet0/0.10
 encapsulation dot1Q 10
 ip address 172.16.50.254 255.255.255.0
 no shutdown
!
interface GigabitEthernet0/0.20
 encapsulation dot1Q 20
 ip address 172.16.60.254 255.255.255.0
 no shutdown
!

Problems I ran into

ProblemDiagnosisFix
Devices unreachable after a restart: the emulated switches lost all VLANs, including admin VLAN 99.vlan.dat is never written to flash. R2 had a route tied to an IP SLA probe that fell with the VLAN. Found with a ping from the VM, show ip interface brief, show interfaces trunk and show track.VLANs recreated by hand (basic administration, outside the generated config), then Nornir rerun only on the failed hosts. VLAN persistence not solved.
A NAPALM diff that is never empty.On IOS, merge mode compares text: vrrp preempt, no shutdown and priority 100 are defaults missing from the running config, so they reappear in every diff.Accepted: the configuration is idempotent in effect, not in the text diff. Templates that skip IOS defaults are on the to-do list.
A NetBox change that seemed invisible.The dry-run did not show the expected change. Reading the API and the Changelog showed it had never been saved.Change made again, then checked through the API.

In progress / next

  • TP-06: detect drift between intent and network, orchestrate, and a GitHub Actions CI pipeline. Subject not published yet.
  • TP-07: the final test, the whole chain. Not started.
  • Not verified all the way: the NAPALM rollback test (TP-03, deliberate error on the VRRP VIP) and the OSPF failover test when the R1-LYON to CORE-1 link is cut (TP-04).
  • To improve: VLAN persistence on the emulated switches, and templates that do not write IOS defaults, to get an empty NAPALM diff.
All the details

Context and principle

  • WAN DevNet course, CPE Lyon, 4IRC (2026). Individual work on a lab emulated in GNS3. The teacher provides the subjects, the lab and code skeletons; I write the requested functions, the data and the templates, then apply them to the network.
  • The network is described as data, first in YAML then in NetBox. The data is validated in Python, then turned into Cisco IOS configurations by Jinja2 templates. The configurations are deployed automatically with Netmiko, NAPALM and Nornir. The project follows the Network Automation Forum framework: intent, executor, then observability.
  • This page describes the work. It does not contain the code of the lab exercises, which are graded.

The network

  • 3 sites: CPE-LYON (head office), CPE-MARS (Marseille) and CPE-PARS (Paris, branch), linked to a core of two routers (CORE-1, CORE-2) provided by the teacher.
  • Devices: 5 site routers (Cisco c7200), 3 switches (c3725 with an EtherSwitch module) and PCs.
  • Protocols: VLANs 10 and 20, 802.1Q trunks, inter-VLAN routing with subinterfaces, VRRP (two routers at Lyon and Marseille), OSPF area 0 towards the core, and a separate administration network (VLAN 99).

Tools

  • Python 3.12, PyYAML, Jinja2, yamllint, pytest, Netmiko, NAPALM, Nornir (nornir-netmiko, nornir-napalm, nornir-netbox), NetBox (Docker), pynetbox, requests, GNS3, Git and GitHub (two remotes: upstream for the teacher, origin for my private repository).

TP-01 to TP-03

  • TP-01: manual configuration of the Lyon site (VLAN, trunk, dot1Q subinterfaces, VRRP), the 3 sites described in YAML, and load_yaml() and summarize() written by me.
  • TP-02: check_site() with 4 checks (unique VLAN ids, gateway inside its network, router IPs in the right network, different VRRP priorities when there are two routers), site_switch.j2 and site_router.j2 templates (VRRP only when the site has two routers), render functions producing 8 config files. The 6 validation tests and 3 rendering tests pass. Marseille configs applied in GNS3: R1-MARS is VRRP Master, R2-MARS is Backup.
  • TP-03: Netmiko (connect, show, deploy_file, backup) with Paris deployed by script and configuration backups before and after; NAPALM (open_device, preview with compare_config, apply with diff, confirmation, then commit or discard). A VLAN description added in the YAML, then the template, applied on R1-MARS and R2-MARS after reading the diff; on the second pass it no longer appears in the diff.

TP-04 and TP-05

  • TP-04: Nornir inventory of 10 devices with groups by role and by site, credentials read from an .env file ignored by Git; init_nornir(), site_routers() (5 routers, never the core), render_ospf() and deploy_ospf() with an ospf.j2 template. OSPF deployed in parallel on the 5 routers in about 12 s; R1-PARS has both neighbours (CORE-1 and CORE-2) in FULL.
  • TP-05: NetBox in Docker; REST API with curl then requests, HTTP codes tested with no token or an invalid one; get_api() and get_or_create() import the 3 sites idempotently; list_sites() and _vlans() read NetBox back and give exactly the same dictionary as the YAML (--compare OK), and the generated configs are identical (diff -r); site_devices() and deploy_site() deployed the VRRP priority change of R2-LYON made in NetBox.

Contact

Email meLinkedIn

Based in Lyon, France

Last updated

Download CVContact