What this demo shows
Two readers land on this page. One is evaluating Infrahub and has no interest in optics. The other works in optics every day and already owns a planner and a controller. The first half below is written for the first reader and the second half for the second. The last two sections are for both.
If you want the result before the reading, the quick start takes you from a fresh clone to a merge Infrahub refuses.
For the network automation engineer​
Each row below names a capability first, then the scenario in this demo that exercises it, then a link to the page that walks it. The optical detail is the example rather than the subject. A wavelength here plays the part a prefix, a VLAN or a circuit would play in your own domain, and the mechanism is the same one you would use.
The capability map​
| Infrahub capability | What this demo does with it | Scenario, and where it is walked |
|---|---|---|
| Branches | Every service is provisioned on a branch cut from the default one. The plant sits on the default branch and no service is ever written there. | Scenario one, create the branch and the request |
| Proposed changes | Opening a proposed change from the branch runs the whole pipeline: fourteen checks, one generator and three artifact definitions, each reported on the change. The branch has to be created with --sync-with-git for that, or the change runs the two built-in validators and stops. | Scenario one, review and merge |
| Checks that gate a merge | .infrahub.yml registers fourteen check definitions. provisionable reads status and fails the change for a service the generator left at rejected that nobody signed for. The reason code and the detail are what make the message readable. | Madrid to Warsaw at DP-16QAM, the regenerator pair and the quick start |
| Generators | One generator definition turns a service request that names no route into a carrier, an optical path, 25 ordered hops and two containers. | Scenario one, run the generator |
| Artifacts | Three artifact definitions: a JSON service trace on every service, and two SVG maps rendered once per PoP, each from its own stored query. | Scenario five and the two maps every PoP carries |
| Python transforms | Ten transforms, each bound to its own stored query, render every report in the demo from live data. | Scenario eight, the AI services against their budgets |
| Stored GraphQL queries | 25 queries, one per report or check, so a query two consumers share is never a query neither can change. | Adding a report |
| Object files in Git | objects/ holds the plant and loads on every init. demo/ holds scenario input and is loaded by hand onto a branch. | objects/ against demo/ |
| Schema as code | Eight schema files loaded as one payload, with flat generics composed on the concrete nodes. | The files |
| Computed attributes | 32 of them across the eight files, every one Jinja2, most turning a scaled integer into a value a person can read. | Display attributes |
| Schema constraints | Enums, min_value and max_value, optional: false with a default_value, and uniqueness constraints in the ["relationship", "attribute__value"] form. | The enum and the default_value at the six reason codes, the bounds at the rules that constrain a change, the uniqueness constraint at a regenerated circuit |
on_delete: cascade | Used on the two relationships that own their children: a device's ports, and an optical path's hops. No page walks it. | Not walked |
| Custom menus | menus/otn.yml builds the sidebar as five top-level entries and fifteen leaves, and every kind sets include_in_menu: false so nothing appears twice. | Menu and navigation |
| Groups as targets | A CoreGeneratorGroup fires the generator. A CoreStandardGroup scoped to the PoPs is what the two map artifacts render against. | Scenario one, create the branch and the request |
| The repository as a Git integration | The schema, the object files, the checks, the generator and the transforms all come from this repository. invoke init registers it, and that is what turns the pipeline on. | Registering the repository |
How a check decides a merge​
Three facts carry most of the weight, and they are worth stating on their own because they are the part that translates directly to your domain.
Any check that logs an error blocks the merge. log_error blocks and
log_info annotates. There is no log_warning in between, so a check has no
way to soften a finding into advice.
A check can be global rather than targeted. provisionable is registered
with no targets, so it judges every service on the branch instead of the ones
your change touched. Adding a span, retiring a mode or filling a corridor can
make a service nobody edited impossible to build, and a targeted check bound
to the edited objects would report green over it.
A refusal can be signed for. refusal_accepted is a Boolean on the
service with default_value: false. Set it and the branch merges with the
refusal on the record, which is why scenario two merges: the 400G on the
saturated Frankfurt to Milan corridor is refused for slots, and
demo/90_fra_mil_saturated.yml signs for that refusal. The scenario that
actually holds a merge shut is demo/06_mad_waw_16qam.yml, where nothing signs
for anything. Concepts
has the decision tree.
For the optical engineer​
An optical team does three jobs with a corridor. Design it, turn it up, and then answer questions about it for as long as it carries traffic: which services a duct cut takes down, whether two circuits are diverse, what is committed on a corridor nobody has built yet.
The first two jobs have tools. A planning tool designs the corridor and needs no live equipment to do it. A domain controller turns the service up, and it holds planned objects too. Both are scoped, the planning tool to one project and one vendor, the controller to one domain. The third job crosses every vendor, every layer and every project, which is why it usually ends up in a spreadsheet.
This demo holds that third record: versioned, held in one graph, and reviewed before it becomes true. It runs no propagation simulation, writes to no device, and reconciles against no external system. What this demo does not exercise is the full list.
Where this sits next to your planner and your NMS​
| Question | Offline planner | Domain controller | This demo |
|---|---|---|---|
| Design a corridor: amplifier siting, budget, bill of materials | Yes. This is the tool for it. | No | No. It holds the result of that work, not the work |
| Receive power and error rate on a channel right now | No | Yes, with history | The last reading only, carried as a fact on a monitor |
| Turn the service up on the hardware | No | Yes | No. Nothing here writes to a device |
| What is committed but not yet built, across vendors | Inside one project file | As planned objects, inside one domain | On a branch, across the whole plant |
| Why this route and not the other one | With whoever ran the tool | No | The path, its ordered hops, the running budget, and the proposed change where a person signed |
| What drops if one duct is cut | Within its own project | Within its own domain | Scenario four |
| Where a coloured pluggable in a router lands on the line system | It is not that tool's transponder | Split across two controllers | One chain of objects, the one below. Three OpenZR+ wavelengths run router to router here. A 400ZR reaches none of these sections, and the model says so and says why |
| Which IP link rides which wavelength rides which duct | No | No | One query, and scenario seven reads it |
| What the plan looked like last quarter | An older file, if anyone kept it | Rarely | The branch and the commit history |
The first three rows are jobs the planning tool and the controller already do, and this demo does not attempt any of them. The remaining six fall between the two tools.
What varies by vendor, and where the model puts it​
| What varies | Where the model puts it | What a second vendor costs |
|---|---|---|
| The mode catalog: reach, required OSNR, baud, forward error correction | OtnOpticalMode objects in objects/03_optical_modes.yml | More objects. No schema change and no code change |
| How much spectrum a mode occupies | A width on the mode, fitted against OtnFrequencyGrid | Nothing. channel_collision reads the width and never asks who built the card |
| Insertion loss and noise figure | vendor and model on OtnOpticalElement, with the loss fields beside them | Objects per model |
| An alien wavelength on a line system another operator built | A transponder whose carrier crosses sections built from other equipment | Nothing. No relationship in the hop chain is scoped to a vendor |
| Framing and correction latency | fec_latency_ns on the mode, framing_latency_ns on the ODU switch | Values per model, added up by scenario eight |
| Which optic supplies a mode | OtnTransceiverType, with the modes each part number supports | More catalog entries. transceiver_mode_support refuses a mode the fitted part cannot run, whoever made it |
| Naming and slot conventions | The human_friendly_id on each kind | A different string |
Every racked element in this plant loads as vendor: Generic. The field
exists and the dataset leaves it unnamed, so the table above says where a
vendor difference goes rather than which vendor. A second vendor is more
objects in objects/, reviewed on a branch like any other change.
One model, every layer it touches​
| Layer | What the demo holds | Kinds |
|---|---|---|
| Duct and shared risk | Which routes share a duct, which is what makes two services not diverse | OtnConduit |
| Fiber | Attenuation, dispersion, group index, Raman gain where it is pumped, and loss added on purpose | OtnFiberSpan, OtnFiberType, OtnRamanPump, OtnFixedAttenuator, OtnVariableAttenuator |
| Spectrum | The 50 GHz grid, the coarse grid, what each carrier occupies on both, and which fiber a channel lands on | OtnFrequencyGrid, OtnCwdmChannel, OtnMuxClientPort, OtnMuxLinePort |
| Optical carrier | One provisioned wavelength, the mode it runs, and every section it crosses | OtnOpticalCarrier, OtnOpticalMode |
| Digital wrapper | The container hierarchy, tributary slots, and where a wavelength terminates and the next one starts | OtnContainer, OtnOduSwitch |
| Client handover | Ethernet, SDH, PDH, Fibre Channel and InfiniBand, each with the container it maps into | OtnClientSignal |
| Router edge | The router, its grey port as the endpoint a service names, and the coloured line port where a pluggable puts a wavelength straight on the line system | OtnRouter, OtnRouterPort, OtnLinePort, OtnTransceiver, OtnTransceiverType |
| Above that | Nothing. No VLAN, no prefix, no routing protocol | Not exercised |
The last row counts as much as the others. This model stops at the router port. A layer boundary is a relationship like any other, so reaching one layer further up is the same work as the layers already modelled here.
Other repositories have done that work for the layers above. infrahub-demo-dc models a data center down to cables, VLANs and IP space. infrahub-demo-sp models a service provider core with MPLS, BGP and L3VPN services. infrahub-solution-ai-dc models an AI data center from the physical location up through the routing overlay to the workload. This repository is the layer below all three, and none of them shares code with it. Each defines its layer in schema files, which is what makes a layer a modelling decision rather than a product feature.
From a router port to a duct​
Eight steps, each one a relationship that already exists in the schema.
| Step | Kind | Reached through | What it fixes |
|---|---|---|---|
| 1 | OtnRouterPort | the service endpoint's ports | the interface the IP side sees |
| 2 | OtnClientPort | connected_to | the grey handover |
| 3 | OtnTransponder | device | the card doing the adaptation |
| 4 | OtnLinePort | ports | the coloured line side |
| 5 | OtnOpticalCarrier | carrier | the wavelength and its mode |
| 6 | OtnOpticalMultiplexSection | sections | ROADM to ROADM |
| 7 | OtnFiberSpan | spans | the physical section and its loss |
| 8 | OtnConduit | conduit | the shared risk |
Two IP links are diverse only when step eight differs for both. diversity.py
checks that on every proposed change, and
scenario seven
walks it. The IP team asks whether two links share a duct. The optical team
asks which services a duct cut takes down. Both questions read this chain, so
both teams need it in the same place.
What this demo does not exercise​
Absence read from silence is a bad inference, so the list below is explicit. Each row was checked against the repository rather than recalled.
| Capability | Status here |
|---|---|
| Physical-layer propagation modelling | Not done. The budget is a running total against a mode's required OSNR, walked at the link budget. Nothing here solves for a nonlinear penalty or fits a spectrum plan the way a planning tool does. |
| Live performance monitoring | Not held. A monitor carries its last reading and the timestamp it was taken at, and invoke demo-drift compares that against what the model says it should be. There is no time series and nothing polls anything. |
Resource pools, CoreNumberPool, prefix and address pools | Not used. No pool of any kind is declared. A channel is chosen by fitting a mode's occupied width into free spectrum, which is a bin-packing decision rather than an allocation from a range. |
Profiles, generate_profile | Not used. Shared default values are written into the object files. |
Object templates, generate_template | Not used. The generator writes structure instead, because what it writes depends on which route closed. |
| IPAM, prefixes and addresses | Not used. There is no IpamPrefix and no address anywhere in the schema. The vendored location generic drops LocationHosting deliberately, and that is the relationship that would have brought IPAM in. |
| Transform-backed computed attributes | Not used. All 32 computed attributes are Jinja2 over the node's own fields. Nothing here aggregates over a cardinality-many relationship inside the schema. |
| Jinja2 transforms and device configuration artifacts | Not used. All ten transforms are Python, and no artifact in this demo is a router configuration. |
| Hierarchical locations | Declared and then switched off. The vendored generic carries hierarchical: true, but OtnSite sets parent: "" and children: "", so the fifteen sites are one flat set with no region or country above them. |
| Webhooks and event-driven integration | Not used. Every run here is a command you type or a proposed change you open. |
| Accounts, roles and permissions | Not used. The repository ships no account, no account group and no permission object, and every command runs as the admin token in .env. |
| Synchronisation with an external system | Not used. Nothing in this repository reads from or writes to anything outside Infrahub, a management system or a planning tool included. The monitor readings in objects/ are loaded from files, not polled from equipment. |
Two capabilities look absent from that list and are not. on_delete: cascade
is used, and so are uniqueness constraints of the
["relationship", "attribute__value"] form. Both are in the capability map
above.
Where to go from here​
- Quick start is the shortest path to a blocked merge.
- Provisioning scenarios walk the two scenarios that write: one service that provisions, and one corridor where a 400G is refused and a 100G is not.
- Reporting scenarios walk the six that read, including what a fiber cut takes down and which services are not diverse.
- The link budget is where the optical arithmetic is set out, corridor by corridor, including the two that do not close.
- Developer guide is where to look if you want to add a report, a check or an element of your own.