Files
arista-evpn-vxlan-clab/infrahub/transforms/templates/mlag_yang.j2
Damien Arnodo cf7da535ed feat: Add Infrahub Jinja2 transforms for MLAG configuration (#22) (#26)
## Summary

Closes #22. Implements Infrahub Jinja2 transforms for MLAG configuration, generating Arista-native YANG-compatible JSON payloads from Infrahub intent data.

| File | Description |
|------|-------------|
| `infrahub/transforms/queries/mlag_intent.gql` | GraphQL query — fetches `InfraMlagPeerConfig` by `$device_name`, including MLAG domain attributes, peer/iBGP VLANs, local interface SVI, and peer-link LAG |
| `infrahub/transforms/templates/mlag_yang.j2` | Jinja2 template — renders a JSON object targeting `/arista-mlag-augments:mlag/config`; returns `[]` for devices with no MLAG (spines) |
| `infrahub/transforms/tests/mlag_yang/test.yml` | Test spec: smoke check + unit render tests for `leaf1` and `spine1` |
| `infrahub/transforms/tests/mlag_yang/leaf1/` | Mock input + expected output for leaf1 (full MLAG config, domain `leafs-1-2`) |
| `infrahub/transforms/tests/mlag_yang/spine1/` | Mock input (empty edges) + expected output `[]` for spine1 |
| `.infrahub.yml` | Registers `mlag_intent` query and `mlag_yang_transform` jinja2 transform |

## Output shape (leaf1)

```json
{
  "mlag": {
    "config": {
      "domain-id": "leafs-1-2",
      "peer-link": "Port-Channel999",
      "local-interface": "Vlan4090",
      "peer-address": "10.0.199.255",
      "shutdown": false
    },
    "dual_primary_detection": {
      "enabled": true,
      "delay": 10,
      "action": "errdisable",
      "heartbeat_peer_ip": "172.16.0.50",
      "heartbeat_vrf": "mgmt"
    },
    "virtual_mac": "c001.cafe.babe",
    "peer_vlan_id": 4090,
    "ibgp_vlan_id": 4091,
    "local_interface_ip": "10.0.199.254/31"
  }
}
```

## Validation

```bash
infrahubctl render mlag_yang_transform device_name=leaf1   # → MLAG config JSON
infrahubctl render mlag_yang_transform device_name=spine1  # → []
```

## Design notes

- Follows identical conventions to existing transforms (#20 VLAN/interface/VXLAN, #21 VRF/L3VNI)
- All optional relationship accesses wrapped in `is defined and is not none` guards
- Spine/non-MLAG devices return `[]` (empty array) — consistent with the "no data → empty collection" pattern used in vxlan_yang for devices without VTEP
2026-03-01 11:06:55 +00:00

91 lines
3.3 KiB
Django/Jinja

{#
mlag_yang.j2 — Produce a JSON object with the MLAG configuration payload
for a single device, targeting Arista-native YANG paths.
Input: GraphQL response from mlag_intent query.
Returns an empty array [] if the device has no MLAG peer config (e.g. spines).
Output structure:
{
"mlag": {
"config": { ... }, # /arista-mlag-augments:mlag/config
"dual_primary_detection": { ... },
"virtual_mac": "...",
"peer_vlan_id": ...,
"ibgp_vlan_id": ...,
"local_interface_ip": "..."
}
}
#}
{%- set peer_configs = data.InfraMlagPeerConfig.edges -%}
{%- if peer_configs | length == 0 -%}
[]
{%- else -%}
{%- set cfg = peer_configs[0].node -%}
{#— Resolve local interface name —#}
{%- set local_iface_name = none -%}
{%- if cfg.local_interface is defined and cfg.local_interface is not none and cfg.local_interface.node is not none -%}
{%- set local_iface_name = cfg.local_interface.node.name.value -%}
{%- endif -%}
{#— Resolve peer-link name —#}
{%- set peer_link_name = none -%}
{%- if cfg.peer_link is defined and cfg.peer_link is not none and cfg.peer_link.node is not none -%}
{%- set peer_link_name = cfg.peer_link.node.name.value -%}
{%- endif -%}
{#— Resolve MLAG domain attributes —#}
{%- set domain_id = none -%}
{%- set virtual_mac = none -%}
{%- set heartbeat_vrf = none -%}
{%- set dual_primary_detection = false -%}
{%- set dual_primary_delay = none -%}
{%- set dual_primary_action = none -%}
{%- set peer_vlan_id = none -%}
{%- set ibgp_vlan_id = none -%}
{%- if cfg.mlag_domain is defined and cfg.mlag_domain is not none and cfg.mlag_domain.node is not none -%}
{%- set domain = cfg.mlag_domain.node -%}
{%- set domain_id = domain.domain_id.value -%}
{%- set virtual_mac = domain.virtual_mac.value | default(none) -%}
{%- set heartbeat_vrf = domain.heartbeat_vrf.value | default("mgmt") -%}
{%- set dual_primary_detection = domain.dual_primary_detection.value | default(false) -%}
{%- set dual_primary_delay = domain.dual_primary_delay.value | default(none) -%}
{%- set dual_primary_action = domain.dual_primary_action.value | default(none) -%}
{%- if domain.peer_vlan is defined and domain.peer_vlan is not none and domain.peer_vlan.node is not none -%}
{%- set peer_vlan_id = domain.peer_vlan.node.vlan_id.value -%}
{%- endif -%}
{%- if domain.ibgp_vlan is defined and domain.ibgp_vlan is not none and domain.ibgp_vlan.node is not none -%}
{%- set ibgp_vlan_id = domain.ibgp_vlan.node.vlan_id.value -%}
{%- endif -%}
{%- endif -%}
{%- set result = {
"mlag": {
"config": {
"domain-id": domain_id,
"peer-link": peer_link_name,
"local-interface": local_iface_name,
"peer-address": cfg.peer_address.value,
"shutdown": false
},
"dual_primary_detection": {
"enabled": dual_primary_detection,
"delay": dual_primary_delay,
"action": dual_primary_action,
"heartbeat_peer_ip": cfg.heartbeat_peer_ip.value,
"heartbeat_vrf": heartbeat_vrf
},
"virtual_mac": virtual_mac,
"peer_vlan_id": peer_vlan_id,
"ibgp_vlan_id": ibgp_vlan_id,
"local_interface_ip": cfg.local_interface_ip.value
}
} -%}
{{ result | tojson(indent=2) }}
{%- endif -%}