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ā
| Path | Purpose |
|---|---|
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-01ā¦pe-08), in a partial mesh (an 8-node ring plus four
cross-chords = 12 p2p links, every PE at degree 3) over a full iBGP mesh. 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. This keeps the
demo focused on the L3VPN-as-a-service story. 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.
L3VPN service flowā
When an operator creates an L3VPN through the Streamlit catalog:
- The catalog allocates a
vpn_idfrom thevpn_id_poolnumber pool. - It opens a feature branch and creates
ServiceL3Vpn+ one or moreServiceL3VpnSiteobjects. - It adds the
ServiceL3Vpnto thel3vpnsgroup. A group-membership trigger (objects/events/00_triggers.yml) firesL3VpnGeneratoron the branch, which materialises the VRF, route targets, PE-CE interfaces, IP addresses, and an eBGP session if the routing protocol is set toebgp. 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. - Once the generator finishes, per-PE configuration artifacts are rendered by the transform layer against the now-complete branch data.
- The catalog opens a
CoreProposedChangetargetingmain. - The proposed-change checks run in the pipeline ā any failure blocks the merge.
- 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:
| Vendor | Transform | Template |
|---|---|---|
| Arista EOS | transforms/pe_arista_eos.py | templates/pe_arista_eos.j2 |
| Cisco IOS-XR | transforms/pe_cisco_iosxr.py | templates/pe_cisco_iosxr.j2 |
| Juniper Junos | transforms/pe_juniper_junos.py | templates/pe_juniper_junos.j2 |
| Nokia SR OS | transforms/pe_nokia_sros.py | templates/pe_nokia_sros.j2 |
| Containerlab | transforms/clab_topology.py | templates/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:
| Check | What it enforces |
|---|---|
l3vpn_overlap | No duplicate VPN IDs across active L3VPNs |
l3vpn_site_subnet | Customer subnet is reachable / not already allocated in the same VRF |
pe_interface_alloc | The nominated PE interface is free (status = free) |
backbone_session_count | Every 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_backbone | Batfish-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:
| Vendor | Macro | Ethernet1 becomes |
|---|---|---|
| Arista EOS | (no translation) | Ethernet1 |
| Cisco IOS-XR | iosxr_iface | GigabitEthernet0/0/0/0 |
| Juniper Junos | junos_iface | ge-0/0/0 |
| SR Linux (lab substitute) | srl_iface | ethernet-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.