Skip to main content

Architecture

This page summarises the data flow and the role of each directory.

Data flow

Schema definition → Bootstrap data → Generators → Transforms → Artifacts

Checks

Directory map

PathPurpose
schemas/base/Core node definitions copied from schema-library
schemas/extensions/VRF, routing, BGP, topology extensions from schema-library
schemas/sp/SP-demo-specific schemas (MPLS, L3VPN service, PE role)
objects/Pre-loaded bootstrap data (PEs, backbone, pools, tenants)
generators/L3VpnGenerator materialises VRF + interfaces + IPs
transforms/One Python+Jinja transform per vendor + the clab transform
templates/Jinja2 templates (one per vendor + clab + macros)
checks/Five checks gating the proposed-change pipeline (incl. Batfish)
service_catalog/Streamlit sidecar (Dashboard + Create L3VPN)
queries/GraphQL queries used by generators, transforms, checks
menus/Sidebar menu configuration
lab/Runtime-only; not committed

Backbone

The MPLS backbone is static demo data and is dataset-specific — each dataset ships its own backbone. The default financial dataset has 8 PEs, all Arista cEOS (pe-01pe-08), in a partial mesh of 15 p2p links over a full iBGP mesh. pe-01 (London) and pe-08 (Zurich) are the two-degree hub PEs that carry every customer attachment; pe-02pe-07 form the meshed core between them at degree 4–5. The isp dataset has 4 PEs, one per vendor, in a full mesh (6 p2p links). Both run ISIS L2 for the underlay and LDP for label distribution. To add or move PEs, edit objects/datasets/<dataset>/60_backbone.yml (and 70_topology.yml for the p2p links) by hand and rerun invoke bootstrap.

Customer edge

The financial dataset also ships four pre-provisioned CE routers (objects/datasets/financial/65_ce.yml), two per customer, attached to the hub PEs:

CECustomer (tenant)PEL3VPN
ce-trading-lonmarkets-tradingpe-01 Ethernet3trading-floor-vpn
ce-trading-zrhmarkets-tradingpe-08 Ethernet3trading-floor-vpn
ce-ib-loninvestment-bankingpe-01 Ethernet4ib-advisory-vpn
ce-ib-zrhinvestment-bankingpe-08 Ethernet4ib-advisory-vpn

They are Arista cEOS boxes with their own rendered configuration artifact (ce-arista-eos), so the PE-CE eBGP session actually establishes in the containerlab topology rather than being modelled only.

The PE ports they land on are seeded in 60_backbone.yml with status: active and a description matching the L3VPN generator's idempotency key (L3VPN <vpn name>). That is what pins the wiring: the generator binds the site to that exact port instead of allocating the next free one, and the port is already out of the free pool so no later service can claim it.

Customer autonomous system numbers

PE-CE eBGP peer ASNs come from the customer_asn_pool number pool (65100–65199, objects/50_pools.yml). The L3VPN generator allocates one AS per VPN — a customer is one routing domain across its sites — and links it as customer_asn on the VPN. A site may still set bgp_peer_asn to override it, which is how the isp dataset peers with pre-agreed customer AS numbers; when every eBGP site overrides, no pool ASN is consumed.

L3VPN service flow

When an operator creates an L3VPN through the Streamlit catalog:

  1. The catalog allocates a vpn_id from the vpn_id_pool number pool.
  2. It opens a feature branch and creates ServiceL3Vpn + one or more ServiceL3VpnSite objects.
  3. It adds the ServiceL3Vpn to the l3vpns group. A group-membership trigger (objects/events/00_triggers.yml) fires L3VpnGenerator on the branch, which materialises the VRF, route targets, PE-CE interfaces, IP addresses, and an eBGP session if the routing protocol is set to ebgp, all inside the customer's own IP namespace. The generator runs event-driven rather than in the proposed-change pipeline (execute_in_proposed_change: false) so its data lands before artifacts render — otherwise the pipeline races and the configuration diff comes out empty. A second rule in the same file re-runs the generator when a site is added to or removed from a service that is already in the group; see services/l3vpn for what that rule cannot cover.
  4. Once the generator finishes, per-PE configuration artifacts are rendered by the transform layer against the now-complete branch data.
  5. The catalog opens a CoreProposedChange targeting main.
  6. The proposed-change checks run in the pipeline — any failure blocks the merge.
  7. The operator reviews the diff in the Infrahub UI and merges.

See services/l3vpn for the full service reference.

Transform layer

Each vendor has a dedicated Python transform module (in transforms/) backed by a Jinja2 template (in templates/). The transform fetches the full PE state via GraphQL and renders a single, complete device configuration fragment. The mapping is:

VendorTransformTemplate
Arista EOStransforms/pe_arista_eos.pytemplates/pe_arista_eos.j2
Cisco IOS-XRtransforms/pe_cisco_iosxr.pytemplates/pe_cisco_iosxr.j2
Juniper Junostransforms/pe_juniper_junos.pytemplates/pe_juniper_junos.j2
Nokia SR OStransforms/pe_nokia_sros.pytemplates/pe_nokia_sros.j2
Containerlabtransforms/clab_topology.pytemplates/clab_topology.j2

All four per-vendor PE transforms are still in play: the isp dataset has one PE per vendor and exercises all of them. The default financial backbone is all-Arista, so it only renders through pe_arista_eos.

Checks

Five checks gate the proposed-change pipeline:

CheckWhat it enforces
l3vpn_overlapNo duplicate VPN IDs across active L3VPNs
l3vpn_site_subnetCustomer subnet is reachable / not already allocated in the same VRF
pe_interface_allocThe nominated PE interface is free (status = free)
backbone_session_countEvery PE has its full-mesh complement of N−1 iBGP sessions (N = PE count: 7 each for the 8-PE financial backbone, 3 each for the 4-PE isp backbone)
batfish_backboneBatfish-driven static validation of rendered backbone configs (parse status, undefined references, BGP session compatibility, IS-IS adjacency mesh). See Batfish validation.

Per-vendor interface name macros

The schema uses an abstract interface name (Ethernet1, Ethernet2, …); each vendor template translates that to the platform-native form via a macro in templates/_macros.j2:

VendorMacroEthernet1 becomes
Arista EOS(no translation)Ethernet1
Cisco IOS-XRiosxr_ifaceGigabitEthernet0/0/0/0
Juniper Junosjunos_ifacege-0/0/0
SR Linux (lab substitute)srl_ifaceethernet-1/1

Without these macros the rendered IOS-XR / Junos configs would carry Arista-style Ethernet<N> interface names — invalid on the target platform and reported as parse errors by BatfishBackboneCheck.

Schema layering

Schemas are loaded in three passes so each layer can reference the previous:

invoke bootstrap
└── infrahubctl schema load schemas/base/
└── infrahubctl schema load schemas/extensions/
└── infrahubctl schema load schemas/sp/

The SP layer adds ServiceL3Vpn, ServiceL3VpnSite, TopologyMplsBackbone, MplsIsisProcess, MplsLdpProcess, and MplsBgpProcess. See schema-reference for field-level details.