diff --git a/.gitignore b/.gitignore
index 8f8e184..c319461 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.DS_Store
containerlab/cEOS64-lab-4.35.0F.tar.xz
-containerlab/clab-arista-l5-dual-dc
\ No newline at end of file
+containerlab/clab-arista-l5-dual-dc
+.envrc
diff --git a/envrc.sample b/envrc.sample
new file mode 100644
index 0000000..575f547
--- /dev/null
+++ b/envrc.sample
@@ -0,0 +1,2 @@
+export INFRAHUB_ADDRESS="https://infrahub.mylab.net"
+export INFRAHUB_API_TOKEN="tata-34da-toto-35db-test"
diff --git a/infrahub/design.md b/infrahub/design.md
new file mode 100644
index 0000000..cd53932
--- /dev/null
+++ b/infrahub/design.md
@@ -0,0 +1,604 @@
+# Data Center Design Philosophy & Automation Model
+
+## 🎯 Core Concept: Datacenter as a Generator
+
+### The Vision
+A **Datacenter** is a **generator object** in Infrahub that creates all child objects automatically based on parent attributes and business logic.
+
+```
+Input: Datacenter Object (with attributes)
+ ↓
+Business Logic (naming conventions, network design patterns)
+ ↓
+Output: Complete infrastructure (devices, interfaces, IPs, BGP sessions)
+```
+
+---
+
+## 📋 Hierarchical Data Model
+
+### Level 0: Organization (Root)
+```
+Organization
+├── Sites (geographic locations)
+└── IP Address Space (RFC 1918 allocations)
+```
+
+### Level 1: Site (Geographic)
+```
+Site
+├── Name: "Paris-DC1"
+├── Location: "Paris, France"
+├── Type: "Production"
+└── Parent Subnet: 10.0.0.0/8
+```
+
+### Level 2: Datacenter (Logical Fabric)
+```
+Datacenter
+├── Site: Paris-DC1
+├── DC ID: 1 (for IP addressing: 10.1.x.x)
+├── Number of Bays: 2 (scalable)
+├── Spine Count: 3 (fixed for now)
+├── Border Leaf Count: 2 (fixed for now)
+└── Generates:
+ ├── Devices (Spines, Leafs, Border Leafs, Access switches)
+ ├── IP Subnets (loopbacks, P2P, MLAG, tenants)
+ ├── Interfaces (physical, loopbacks, VLANs)
+ ├── BGP Configuration (ASNs, peers)
+ └── MLAG Pairs
+```
+
+### Level 3: Bay/Rack (Physical)
+```
+Bay
+├── Bay ID: 1, 2, 3...
+├── Access Switch: access{bay_id}-DC{dc_id}
+├── Leaf Pair: Assigned based on bay_id (round-robin)
+├── Host Count: Variable (1-48 per bay)
+└── Generates:
+ ├── Access Switch device
+ ├── Access-to-Leaf connections
+ ├── Host connections
+ └── VLAN assignments
+```
+
+### Level 4: Devices (Auto-generated)
+```
+Device
+├── Hostname: (from naming convention)
+├── Role: spine|leaf|borderleaf|access
+├── Interfaces: (auto-generated based on role)
+├── IP Addresses: (auto-allocated from parent subnets)
+├── BGP Config: (auto-generated based on role)
+└── MLAG Config: (if part of pair)
+```
+
+---
+
+## 🏗️ Datacenter Generator - Input Attributes
+
+### Required Attributes
+```yaml
+name: "DC1" # Human-readable name
+dc_id: 1 # Numeric ID (for IP addressing)
+site: "Paris-DC1" # Parent site reference
+parent_subnet: "10.0.0.0/8" # Address space allocation
+number_of_bays: 2 # Initial bay count (scalable)
+has_border_leafs: true # Include border leafs for DCI capability
+```
+
+### Optional Attributes (with defaults)
+```yaml
+spine_count: 3 # Default: 3
+spine_asn: 65100 # Default: 65000 + (dc_id * 100)
+border_leaf_count: 2 # Default: 2 (0 if has_border_leafs: false)
+bgp_base_asn: 65000 # Default: 65000
+mlag_domain_id: "MLAG" # Default: "MLAG"
+mtu: 9214 # Default: 9214
+dci_enabled: false # Default: false (configure eth12 as shutdown)
+```
+
+### Derived/Computed Attributes (auto-calculated)
+```yaml
+# Computed from dc_id
+loopback0_subnet: "10.{dc_id}.0.0/24"
+loopback1_subnet: "10.{dc_id}.1.0/24"
+spine_leaf_p2p_subnet: "10.{dc_id}.10.0/24"
+leaf_access_p2p_subnet: "10.{dc_id}.20.0/24"
+mlag_peer_subnet: "10.{dc_id}.255.0/24"
+
+# Computed from number_of_bays
+leaf_pair_count: ceil(number_of_bays / 2) # 2 bays per leaf pair
+total_leaf_count: leaf_pair_count * 2
+total_access_count: number_of_bays
+```
+
+---
+
+## 🎨 Business Logic Rules
+
+### 1. Naming Conventions
+
+#### Spine Switches
+```
+Pattern: spine{spine_number}-DC{dc_id}
+Example: spine1-DC1, spine2-DC1, spine3-DC1
+```
+
+#### Leaf Switches
+```
+Pattern: leaf{leaf_number}-DC{dc_id}
+Example: leaf1-DC1, leaf2-DC1, leaf3-DC1, leaf4-DC1
+```
+
+#### Border Leaf Switches
+```
+Pattern: borderleaf{number}-DC{dc_id}
+Example: borderleaf1-DC1, borderleaf2-DC1
+```
+
+#### Access Switches
+```
+Pattern: access{bay_id}-DC{dc_id}
+Example: access1-DC1, access2-DC1
+```
+
+#### Hosts
+```
+Pattern: host{host_number}-DC{dc_id}
+Example: host1-DC1, host2-DC1
+```
+
+### 2. Bay-to-Leaf Assignment Logic
+
+**Rule**: Each bay connects to ONE leaf pair (round-robin assignment)
+
+```python
+# Pseudo-code
+leaf_pairs = [
+ [leaf1, leaf2], # MLAG Pair 1
+ [leaf3, leaf4], # MLAG Pair 2
+ [leaf5, leaf6], # MLAG Pair 3 (if exists)
+]
+
+def assign_bay_to_leaf_pair(bay_id, leaf_pairs):
+ pair_index = (bay_id - 1) // 2 # Integer division
+ return leaf_pairs[pair_index]
+
+# Examples:
+# bay_id=1 → leaf_pairs[0] → [leaf1, leaf2]
+# bay_id=2 → leaf_pairs[0] → [leaf1, leaf2]
+# bay_id=3 → leaf_pairs[1] → [leaf3, leaf4]
+# bay_id=4 → leaf_pairs[1] → [leaf3, leaf4]
+```
+
+**Result**:
+- Bay 1 & 2 → Leaf Pair 1 (leaf1-2)
+- Bay 3 & 4 → Leaf Pair 2 (leaf3-4)
+- Bay 5 & 6 → Leaf Pair 3 (leaf5-6)
+
+### 3. MLAG Pairing Logic
+
+**Rule**: Odd-numbered leafs pair with next even-numbered leaf
+
+```python
+def create_mlag_pairs(total_leafs):
+ pairs = []
+ for i in range(1, total_leafs + 1, 2):
+ pairs.append([f"leaf{i}", f"leaf{i+1}"])
+ return pairs
+
+# Example with 4 leafs:
+# → [[leaf1, leaf2], [leaf3, leaf4]]
+```
+
+### 4. Interface Assignment Logic
+
+#### Spine Interfaces
+```
+Role: Connect to all leafs + border leafs
+Pattern:
+ eth2 → leaf1
+ eth3 → leaf2
+ eth4 → leaf3
+ eth5 → leaf4
+ eth6 → borderleaf1
+ eth7 → borderleaf2
+```
+
+#### Leaf Interfaces
+```
+Role: Uplinks to spines, MLAG peer, downlinks to access
+Pattern:
+ eth1-2 → MLAG peer link (to paired leaf)
+ eth3-5 → Uplinks (spine1, spine2, spine3)
+ eth7+ → Downlinks to access switches (2 bays per pair)
+```
+
+#### Access Interfaces
+```
+Role: Uplinks to leaf pair, downlinks to hosts
+Pattern:
+ eth1-2 → Uplinks (to leaf pair via MLAG)
+ eth10+ → Host connections
+```
+
+### 5. IP Address Allocation Logic
+
+#### Management IPs (10.255.0.0/24)
+```python
+def allocate_mgmt_ip(device_role, device_number, dc_id):
+ base_octets = {
+ 'spine': 10 + (dc_id - 1) * 30, # DC1: 11-13, DC2: 41-43
+ 'leaf': 20 + (dc_id - 1) * 30, # DC1: 21-24, DC2: 51-54
+ 'borderleaf': 30 + (dc_id - 1) * 30, # DC1: 31-32, DC2: 61-62
+ 'access': 70 + (dc_id - 1) * 10, # DC1: 71-72, DC2: 81-82
+ 'host': 200 + (dc_id - 1) * 10, # DC1: 201-202, DC2: 211-212
+ }
+ return f"10.255.0.{base_octets[device_role] + device_number - 1}"
+
+# Examples:
+# spine1, DC1 → 10.255.0.11
+# leaf1, DC1 → 10.255.0.21
+# access1, DC2 → 10.255.0.81
+```
+
+#### Loopback0 (Router IDs)
+```python
+def allocate_loopback0(device_role, device_number, dc_id):
+ role_base = {
+ 'spine': 11,
+ 'leaf': 21,
+ 'borderleaf': 31,
+ }
+ return f"10.{dc_id}.0.{role_base[device_role] + device_number - 1}/32"
+
+# Examples:
+# spine1, DC1 → 10.1.0.11/32
+# leaf3, DC1 → 10.1.0.23/32
+# spine2, DC2 → 10.2.0.12/32
+```
+
+#### Loopback1 (VTEP - Shared by MLAG pairs)
+```python
+def allocate_loopback1_vtep(leaf_number, dc_id):
+ # Odd and even leafs share same VTEP
+ vtep_base = ((leaf_number - 1) // 2) * 2 + 21
+ return f"10.{dc_id}.1.{vtep_base}/32"
+
+# Examples:
+# leaf1, DC1 → 10.1.1.21/32 (shared with leaf2)
+# leaf2, DC1 → 10.1.1.21/32 (shared with leaf1)
+# leaf3, DC1 → 10.1.1.23/32 (shared with leaf4)
+# leaf4, DC1 → 10.1.1.23/32 (shared with leaf3)
+```
+
+#### P2P Links (Spine-Leaf)
+```python
+def allocate_spine_leaf_p2p(spine_num, leaf_num, dc_id):
+ # Each spine gets 6 leafs (including borderleafs)
+ # Each link uses /31 (2 IPs)
+ offset = (spine_num - 1) * 12 + (leaf_num - 1) * 2
+ leaf_ip = f"10.{dc_id}.10.{offset}"
+ spine_ip = f"10.{dc_id}.10.{offset + 1}"
+ return (leaf_ip, spine_ip)
+
+# Example:
+# spine1 → leaf1 → (10.1.10.0, 10.1.10.1)
+# spine1 → leaf2 → (10.1.10.2, 10.1.10.3)
+```
+
+#### P2P Links (Leaf-Access)
+```python
+def allocate_leaf_access_p2p(access_num, leaf_num, dc_id):
+ # 2 uplinks per access (to MLAG leaf pair)
+ offset = (access_num - 1) * 4 + (leaf_num % 2) * 2
+ access_ip = f"10.{dc_id}.20.{offset}"
+ leaf_ip = f"10.{dc_id}.20.{offset + 1}"
+ return (access_ip, leaf_ip)
+
+# Example:
+# access1 → leaf1 → (10.1.20.0, 10.1.20.1)
+# access1 → leaf2 → (10.1.20.2, 10.1.20.3)
+```
+
+#### MLAG Peer Links
+```python
+def allocate_mlag_peer_ips(leaf_pair_num, dc_id):
+ # Each pair gets /30 (4 IPs, use 2)
+ offset = (leaf_pair_num - 1) * 4 + 1
+ odd_leaf_ip = f"10.{dc_id}.255.{offset}" # .1, .5, .9
+ even_leaf_ip = f"10.{dc_id}.255.{offset + 1}" # .2, .6, .10
+ return (odd_leaf_ip, even_leaf_ip)
+
+# Example:
+# Pair 1 (leaf1-2) → (10.1.255.1, 10.1.255.2)
+# Pair 2 (leaf3-4) → (10.1.255.5, 10.1.255.6)
+```
+
+### 6. BGP ASN Allocation Logic
+
+```python
+def allocate_asn(device_role, pair_number, dc_id, base_asn=65000):
+ if device_role == 'spine':
+ return base_asn + (dc_id * 100)
+ elif device_role == 'leaf':
+ return base_asn + (dc_id * 100) + pair_number
+ elif device_role == 'borderleaf':
+ # Border leafs get ASN +3 from base
+ return base_asn + (dc_id * 100) + 3
+ else:
+ return None # Access switches don't run BGP
+
+# Examples:
+# DC1 Spines → 65100
+# DC1 Leaf Pair 1 (leaf1-2) → 65101
+# DC1 Leaf Pair 2 (leaf3-4) → 65102
+# DC1 Border Leafs → 65103
+# DC2 Spines → 65200
+```
+
+---
+
+## 📊 Object Relationships & Dependencies
+
+### Dependency Graph
+```
+Organization
+ └── Site
+ ├── IP Space Allocation
+ └── Datacenter (GENERATOR)
+ ├── Computes: leaf_pair_count from number_of_bays
+ ├── Generates Devices:
+ │ ├── Spines (fixed count)
+ │ ├── Leafs (computed count)
+ │ ├── Border Leafs (fixed count)
+ │ └── Access Switches (= number_of_bays)
+ ├── Generates IP Subnets:
+ │ ├── Loopback0 subnet
+ │ ├── Loopback1 subnet
+ │ ├── Spine-Leaf P2P subnet
+ │ ├── Leaf-Access P2P subnet
+ │ └── MLAG Peer subnet
+ ├── For Each Device:
+ │ ├── Allocates Management IP
+ │ ├── Allocates Loopback IPs (if spine/leaf)
+ │ ├── Creates Interfaces (based on role)
+ │ ├── Allocates P2P IPs (for each link)
+ │ └── Generates BGP Config (if spine/leaf)
+ └── Creates MLAG Pairs:
+ └── Assigns peer IPs
+```
+
+### Object Creation Order (to respect dependencies)
+```
+1. Site
+2. IP Prefixes (parent subnets)
+3. Datacenter (generator starts here)
+ ├── 4. Generate Subnets (children of parent subnet)
+ ├── 5. Generate Devices (all roles)
+ ├── 6. Generate MLAG Pairs (relationships)
+ ├── 7. Generate Interfaces (for each device)
+ ├── 8. Allocate IP Addresses (for each interface)
+ ├── 9. Generate BGP ASNs
+ └── 10. Generate BGP Sessions (relationships)
+```
+
+---
+
+## 🔄 Scaling Operations
+
+### Adding a New Bay
+
+**Input**:
+- Datacenter: DC1
+- New bay_id: 3
+
+**Generator Actions**:
+1. Create new access switch: `access3-DC1`
+2. Determine leaf pair assignment:
+ - bay_id=3 → pair_index = (3-1)//2 = 1 → Leaf Pair 2 (leaf3-4)
+3. Create interfaces:
+ - access3-DC1: eth1, eth2 (uplinks)
+ - leaf3-DC1: add port for access3
+ - leaf4-DC1: add port for access3
+4. Allocate IPs:
+ - Management: 10.255.0.73
+ - P2P to leaf3: (10.1.20.8, 10.1.20.9)
+ - P2P to leaf4: (10.1.20.10, 10.1.20.11)
+5. Update datacenter: `number_of_bays: 3`
+
+**Result**: New bay operational with zero manual config!
+
+### Adding a New Leaf Pair
+
+**Trigger**: `number_of_bays > (leaf_pair_count * 2)`
+
+**Example**:
+- Current: 2 leaf pairs support 4 bays
+- Request: bay_id = 5
+- Action: Generate leaf5-DC1 and leaf6-DC1
+
+**Generator Actions**:
+1. Create devices: leaf5-DC1, leaf6-DC1
+2. Create MLAG pair: [leaf5, leaf6]
+3. Allocate IPs:
+ - Loopback0: 10.1.0.25, 10.1.0.26
+ - Loopback1: 10.1.1.25 (shared)
+ - MLAG Peer: 10.1.255.13, 10.1.255.14
+4. Create spine uplinks (3 per leaf)
+5. Allocate BGP ASN: 65104
+6. Generate BGP config
+
+---
+
+## 🎯 Validation Rules
+
+### Pre-Generation Checks
+```python
+def validate_datacenter_input(dc):
+ checks = []
+
+ # Check 1: DC ID must be unique
+ if dc.dc_id in existing_dc_ids:
+ checks.append("ERROR: DC ID already exists")
+
+ # Check 2: Parent subnet must be large enough
+ required_size = calculate_ip_requirements(dc.number_of_bays)
+ if dc.parent_subnet.size < required_size:
+ checks.append("ERROR: Parent subnet too small")
+
+ # Check 3: Number of bays must be positive
+ if dc.number_of_bays < 1:
+ checks.append("ERROR: Must have at least 1 bay")
+
+ # Check 4: ASN must not conflict
+ if asn_conflicts_exist(dc):
+ checks.append("ERROR: ASN conflicts detected")
+
+ return checks
+```
+
+### Post-Generation Checks
+```python
+def validate_generated_objects(dc):
+ checks = []
+
+ # Check 1: All devices created
+ expected_devices = (
+ dc.spine_count +
+ dc.leaf_pair_count * 2 +
+ dc.border_leaf_count +
+ dc.number_of_bays
+ )
+ if len(dc.devices) != expected_devices:
+ checks.append("ERROR: Device count mismatch")
+
+ # Check 2: No duplicate IPs
+ if has_duplicate_ips(dc.devices):
+ checks.append("ERROR: Duplicate IP addresses")
+
+ # Check 3: All BGP sessions configured
+ if missing_bgp_sessions(dc.devices):
+ checks.append("ERROR: Missing BGP sessions")
+
+ # Check 4: MLAG pairs valid
+ if invalid_mlag_pairs(dc.devices):
+ checks.append("ERROR: Invalid MLAG configuration")
+
+ return checks
+```
+
+---
+
+## 📐 Mermaid Diagram Structure
+
+### High-Level Datacenter Diagram
+```mermaid
+graph TB
+ Site[Site: Paris-DC1]
+ DC[Datacenter: DC1
bays=2, spines=3]
+
+ Site --> DC
+
+ DC --> Spines[Spine Layer
spine1-3-DC1]
+ DC --> Leafs[Leaf Layer
leaf1-4-DC1]
+ DC --> Borders[Border Layer
borderleaf1-2-DC1]
+ DC --> Bays[Bay Layer
bay1-2]
+
+ Bays --> Bay1[Bay 1]
+ Bays --> Bay2[Bay 2]
+
+ Bay1 --> Access1[access1-DC1]
+ Bay2 --> Access2[access2-DC1]
+
+ Access1 --> LeafPair1[Leaf Pair 1
leaf1-2-DC1]
+ Access2 --> LeafPair2[Leaf Pair 2
leaf3-4-DC1]
+
+ LeafPair1 --> Spines
+ LeafPair2 --> Spines
+ Borders --> Spines
+```
+
+### Detailed Device-Level Diagram
+```mermaid
+graph LR
+ subgraph DC1
+ S1[spine1-DC1
10.1.0.11]
+ S2[spine2-DC1
10.1.0.12]
+ S3[spine3-DC1
10.1.0.13]
+
+ L1[leaf1-DC1
10.1.0.21
VTEP: 10.1.1.21]
+ L2[leaf2-DC1
10.1.0.22
VTEP: 10.1.1.21]
+ L3[leaf3-DC1
10.1.0.23
VTEP: 10.1.1.23]
+ L4[leaf4-DC1
10.1.0.24
VTEP: 10.1.1.23]
+
+ A1[access1-DC1
Bay 1]
+ A2[access2-DC1
Bay 2]
+
+ L1 -.MLAG.- L2
+ L3 -.MLAG.- L4
+
+ S1 --> L1
+ S1 --> L2
+ S1 --> L3
+ S1 --> L4
+
+ S2 --> L1
+ S2 --> L2
+ S2 --> L3
+ S2 --> L4
+
+ S3 --> L1
+ S3 --> L2
+ S3 --> L3
+ S3 --> L4
+
+ L1 --> A1
+ L2 --> A1
+ L3 --> A2
+ L4 --> A2
+ end
+```
+
+---
+
+## 🚀 Summary: What Infrahub Will Generate
+
+**From Single "Datacenter" Object Input**:
+
+✅ **27 Device Objects** (for DC with 2 bays):
+- 3 Spines
+- 4 Leafs
+- 2 Border Leafs
+- 2 Access switches
+
+✅ **~150 Interface Objects**:
+- Physical interfaces
+- Loopback interfaces
+- VLAN interfaces (MLAG)
+
+✅ **~90 IP Address Objects**:
+- Management IPs
+- Loopback IPs
+- P2P link IPs
+- MLAG peer IPs
+
+✅ **~40 BGP Session Objects**:
+- Spine-to-Leaf sessions
+- Leaf MLAG iBGP sessions
+- Border-to-DCI sessions
+
+✅ **3 MLAG Pair Objects**:
+- Leaf pairs
+- Border leaf pair
+
+✅ **6 Subnet Objects**:
+- Loopback0, Loopback1
+- Spine-Leaf P2P, Leaf-Access P2P
+- MLAG Peer, Tenant VLANs
+
+**All from ~10 input attributes!** 🎯
+
+This is the power of the generator pattern - infrastructure as data!
\ No newline at end of file
diff --git a/infrahub/generator.md b/infrahub/generator.md
new file mode 100644
index 0000000..fecdb8f
--- /dev/null
+++ b/infrahub/generator.md
@@ -0,0 +1,538 @@
+# Datacenter Generator - Visual Diagrams
+
+## 1. Generator Concept - High Level
+
+```mermaid
+graph TB
+ subgraph Input["📥 INPUT"]
+ DC1[Datacenter: DC1
---
dc_id: 1
bays: 2
dci_enabled: false]
+ DC2[Datacenter: DC2
---
dc_id: 2
bays: 2
dci_enabled: false]
+ DCI_Config[DCI Configuration
---
Manual/External
IP: 10.253.254.x]
+ end
+
+ subgraph Logic["⚙️ DATACENTER GENERATOR"]
+ Gen1[DC1 Generator:
Create 11 devices
Border eth12: shutdown]
+ Gen2[DC2 Generator:
Create 11 devices
Border eth12: shutdown]
+ end
+
+ subgraph Output1["📤 DC1 OUTPUT"]
+ Dev1[11 Device Objects]
+ Int1[~50 Interface Objects]
+ IP1[~30 IP Addresses]
+ end
+
+ subgraph Output2["📤 DC2 OUTPUT"]
+ Dev2[11 Device Objects]
+ Int2[~50 Interface Objects]
+ IP2[~30 IP Addresses]
+ end
+
+ subgraph Manual["🔧 MANUAL DCI SETUP"]
+ DCIDevice[DCI Device
Loopback: 10.253.0.1
ASN: 65000]
+ UpdateDC1[Update DC1:
dci_enabled: true
dci_remote_dc_id: 2]
+ UpdateDC2[Update DC2:
dci_enabled: true
dci_remote_dc_id: 1]
+ end
+
+ DC1 --> Gen1
+ DC2 --> Gen2
+
+ Gen1 --> Dev1
+ Gen1 --> Int1
+ Gen1 --> IP1
+
+ Gen2 --> Dev2
+ Gen2 --> Int2
+ Gen2 --> IP2
+
+ Output1 --> Manual
+ Output2 --> Manual
+ DCI_Config --> DCIDevice
+ DCIDevice --> UpdateDC1
+ DCIDevice --> UpdateDC2
+
+ style DC1 fill:#e1f5ff
+ style DC2 fill:#e1f5ff
+ style Manual fill:#fff9c4
+ style DCIDevice fill:#ffccbc
+```
+
+## 2. Datacenter Hierarchy
+
+```mermaid
+graph TB
+ Org[Organization]
+ Site[Site: Paris-DC1
Location: Paris, France]
+ DC[Datacenter: DC1
dc_id: 1
bays: 2
spines: 3]
+
+ Org --> Site
+ Site --> DC
+
+ DC --> SpineLayer[Spine Layer]
+ DC --> LeafLayer[Leaf Layer]
+ DC --> BorderLayer[Border Layer]
+ DC --> BayLayer[Bay Layer]
+ DC --> Subnets[IP Subnets]
+
+ SpineLayer --> S1[spine1-DC1
ASN: 65100]
+ SpineLayer --> S2[spine2-DC1
ASN: 65100]
+ SpineLayer --> S3[spine3-DC1
ASN: 65100]
+
+ LeafLayer --> LP1[Leaf Pair 1
ASN: 65101]
+ LeafLayer --> LP2[Leaf Pair 2
ASN: 65102]
+
+ LP1 --> L1[leaf1-DC1
10.1.0.21]
+ LP1 --> L2[leaf2-DC1
10.1.0.22]
+ LP2 --> L3[leaf3-DC1
10.1.0.23]
+ LP2 --> L4[leaf4-DC1
10.1.0.24]
+
+ BorderLayer --> BP[Border Pair
ASN: 65103]
+ BP --> B1[borderleaf1-DC1]
+ BP --> B2[borderleaf2-DC1]
+
+ BayLayer --> Bay1[Bay 1]
+ BayLayer --> Bay2[Bay 2]
+
+ Bay1 --> A1[access1-DC1]
+ Bay2 --> A2[access2-DC1]
+
+ Subnets --> Sub1[10.1.0.0/24
Loopback0]
+ Subnets --> Sub2[10.1.1.0/24
Loopback1]
+ Subnets --> Sub3[10.1.10.0/24
Spine-Leaf P2P]
+ Subnets --> Sub4[10.1.20.0/24
Leaf-Access P2P]
+
+ style DC fill:#ffecb3
+ style LP1 fill:#b3e5fc
+ style LP2 fill:#b3e5fc
+ style Bay1 fill:#c8e6c9
+ style Bay2 fill:#c8e6c9
+```
+
+## 3. Bay-to-Leaf Assignment Logic
+
+```mermaid
+graph LR
+ subgraph Bays["🏢 Bays"]
+ Bay1[Bay 1
access1-DC1]
+ Bay2[Bay 2
access2-DC1]
+ Bay3[Bay 3
access3-DC1]
+ Bay4[Bay 4
access4-DC1]
+ end
+
+ subgraph LeafPairs["🔀 Leaf Pairs - MLAG"]
+ LP1[Leaf Pair 1
leaf1-DC1 ↔ leaf2-DC1
ASN: 65101]
+ LP2[Leaf Pair 2
leaf3-DC1 ↔ leaf4-DC1
ASN: 65102]
+ end
+
+ subgraph Spines["⬆️ Spine Layer"]
+ S1[spine1-DC1]
+ S2[spine2-DC1]
+ S3[spine3-DC1]
+ end
+
+ Bay1 -.2 uplinks.- LP1
+ Bay2 -.2 uplinks.- LP1
+ Bay3 -.2 uplinks.- LP2
+ Bay4 -.2 uplinks.- LP2
+
+ LP1 --> S1
+ LP1 --> S2
+ LP1 --> S3
+ LP2 --> S1
+ LP2 --> S2
+ LP2 --> S3
+
+ style Bay1 fill:#c8e6c9
+ style Bay2 fill:#c8e6c9
+ style Bay3 fill:#ffccbc
+ style Bay4 fill:#ffccbc
+ style LP1 fill:#b3e5fc
+ style LP2 fill:#b3e5fc
+```
+
+## 4. Complete DC1 Physical Topology
+
+```mermaid
+graph TB
+ subgraph DCI["DCI LAYER - Inter-DC"]
+ DCID[DCI Switch
10.253.0.1
ASN: 65000]
+ end
+
+ subgraph Border["BORDER LEAF - DCI Gateway"]
+ B1[borderleaf1-DC1
10.1.0.31
eth12: shutdown or active]
+ B2[borderleaf2-DC1
10.1.0.32
eth12: shutdown or active]
+ end
+
+ subgraph Spine["SPINE LAYER - L3 Core"]
+ S1[spine1-DC1
10.1.0.11
ASN: 65100]
+ S2[spine2-DC1
10.1.0.12
ASN: 65100]
+ S3[spine3-DC1
10.1.0.13
ASN: 65100]
+ end
+
+ subgraph Leaf["LEAF LAYER - Aggregation + VXLAN"]
+ subgraph Pair1["MLAG Pair 1 - ASN: 65101"]
+ L1[leaf1-DC1
10.1.0.21
VTEP: 10.1.1.21]
+ L2[leaf2-DC1
10.1.0.22
VTEP: 10.1.1.21]
+ end
+ subgraph Pair2["MLAG Pair 2 - ASN: 65102"]
+ L3[leaf3-DC1
10.1.0.23
VTEP: 10.1.1.23]
+ L4[leaf4-DC1
10.1.0.24
VTEP: 10.1.1.23]
+ end
+ end
+
+ subgraph Access["ACCESS LAYER - Rack/Bay ToR"]
+ A1[access1-DC1
Bay 1
L2 Switch]
+ A2[access2-DC1
Bay 2
L2 Switch]
+ end
+
+ subgraph Hosts["HOST LAYER"]
+ H1[host1-DC1
172.16.100.10]
+ H2[host2-DC1
172.16.200.10]
+ end
+
+ DCID -.Optional DCI.- B1
+ DCID -.Optional DCI.- B2
+
+ S1 -.eBGP.- L1
+ S1 -.eBGP.- L2
+ S1 -.eBGP.- L3
+ S1 -.eBGP.- L4
+ S1 -.eBGP.- B1
+ S1 -.eBGP.- B2
+
+ S2 -.eBGP.- L1
+ S2 -.eBGP.- L2
+ S2 -.eBGP.- L3
+ S2 -.eBGP.- L4
+ S2 -.eBGP.- B1
+ S2 -.eBGP.- B2
+
+ S3 -.eBGP.- L1
+ S3 -.eBGP.- L2
+ S3 -.eBGP.- L3
+ S3 -.eBGP.- L4
+ S3 -.eBGP.- B1
+ S3 -.eBGP.- B2
+
+ L1 ---|MLAG| L2
+ L3 ---|MLAG| L4
+ B1 ---|MLAG| B2
+
+ L1 -->|eth7| A1
+ L2 -->|eth7| A1
+ L3 -->|eth7| A2
+ L4 -->|eth7| A2
+
+ A1 -->|eth10| H1
+ A2 -->|eth10| H2
+
+ style DCID fill:#ffccbc
+ style S1 fill:#ffccbc
+ style S2 fill:#ffccbc
+ style S3 fill:#ffccbc
+ style L1 fill:#b3e5fc
+ style L2 fill:#b3e5fc
+ style L3 fill:#b3e5fc
+ style L4 fill:#b3e5fc
+ style A1 fill:#c8e6c9
+ style A2 fill:#c8e6c9
+ style B1 fill:#f8bbd0
+ style B2 fill:#f8bbd0
+```
+
+## 5. IP Address Generation Flow
+
+```mermaid
+graph TB
+ Start[Datacenter Object
dc_id: 1, bays: 2]
+
+ Start --> GenSubnets[Generate Subnets
from dc_id]
+
+ GenSubnets --> Sub1[10.1.0.0/24
Loopback0]
+ GenSubnets --> Sub2[10.1.1.0/24
Loopback1]
+ GenSubnets --> Sub3[10.1.10.0/24
Spine-Leaf P2P]
+ GenSubnets --> Sub4[10.1.20.0/24
Leaf-Access P2P]
+ GenSubnets --> Sub5[10.1.255.0/24
MLAG Peer]
+ GenSubnets --> Sub6[10.255.0.0/24
Management]
+
+ Sub1 --> AllocLo0[Allocate Loopback0
to Spines & Leafs]
+ Sub2 --> AllocLo1[Allocate Loopback1
to Leafs - shared in pairs]
+ Sub3 --> AllocP2P1[Allocate /31s
for Spine-Leaf links]
+ Sub4 --> AllocP2P2[Allocate /31s
for Leaf-Access links]
+ Sub5 --> AllocMLAG[Allocate /30s
for MLAG peers]
+ Sub6 --> AllocMgmt[Allocate Management IPs
to all devices]
+
+ AllocLo0 --> Spine1IP[spine1: 10.1.0.11/32]
+ AllocLo0 --> Leaf1IP[leaf1: 10.1.0.21/32]
+
+ AllocLo1 --> Leaf1VTEP[leaf1-2: 10.1.1.21/32 - shared]
+
+ AllocP2P1 --> Link1[spine1-leaf1: 10.1.10.0/31]
+
+ AllocP2P2 --> Link2[leaf1-access1: 10.1.20.0/31]
+
+ AllocMLAG --> MLAG1[leaf1-2 peer: 10.1.255.1-2/30]
+
+ AllocMgmt --> Mgmt1[spine1: 10.255.0.11]
+
+ style Start fill:#ffecb3
+ style Sub1 fill:#e1f5ff
+ style Sub2 fill:#e1f5ff
+ style Sub3 fill:#e1f5ff
+ style Sub4 fill:#e1f5ff
+ style Sub5 fill:#e1f5ff
+ style Sub6 fill:#e1f5ff
+```
+
+## 6. Device Generation Flow
+
+```mermaid
+graph TB
+ DC[Datacenter: DC1
bays: 2]
+
+ DC --> CalcLeafs[Calculate:
leaf_pairs = ceil - bays / 2 - = 1
total_leafs = 1 * 2 = 2]
+
+ CalcLeafs --> GenSpines[Generate Spines
count: 3 - fixed]
+ CalcLeafs --> GenLeafs[Generate Leafs
count: 4 - computed]
+ CalcLeafs --> GenBorders[Generate Border Leafs
count: 2 - fixed]
+ CalcLeafs --> GenAccess[Generate Access
count: 2 - from bays]
+
+ GenSpines --> S1[spine1-DC1]
+ GenSpines --> S2[spine2-DC1]
+ GenSpines --> S3[spine3-DC1]
+
+ GenLeafs --> LP1[Create MLAG Pair 1]
+ GenLeafs --> LP2[Create MLAG Pair 2]
+
+ LP1 --> L1[leaf1-DC1]
+ LP1 --> L2[leaf2-DC1]
+ LP2 --> L3[leaf3-DC1]
+ LP2 --> L4[leaf4-DC1]
+
+ GenBorders --> BP[Create Border Pair]
+ BP --> B1[borderleaf1-DC1]
+ BP --> B2[borderleaf2-DC1]
+
+ GenAccess --> AssignBays[Assign Bays to Leaf Pairs]
+ AssignBays --> A1Config[Bay 1 → Leaf Pair 1
access1-DC1]
+ AssignBays --> A2Config[Bay 2 → Leaf Pair 1
access2-DC1]
+
+ A1Config --> A1[access1-DC1]
+ A2Config --> A2[access2-DC1]
+
+ style DC fill:#ffecb3
+ style LP1 fill:#b3e5fc
+ style LP2 fill:#b3e5fc
+ style BP fill:#f8bbd0
+```
+
+## 7. Scaling Scenario - Adding Bay 3
+
+```mermaid
+graph LR
+ subgraph Current["📦 Current State - 2 Bays"]
+ CurDC[Datacenter: DC1
bays: 2
leaf_pairs: 1]
+ CurLP1[Leaf Pair 1
leaf1-2]
+ CurBay1[Bay 1 → access1]
+ CurBay2[Bay 2 → access2]
+
+ CurDC --> CurLP1
+ CurLP1 --> CurBay1
+ CurLP1 --> CurBay2
+ end
+
+ subgraph Action["⚙️ User Action"]
+ AddBay[Add Bay 3
number_of_bays: 2 → 3]
+ end
+
+ subgraph Generator["🔄 Generator Logic"]
+ Check[Check:
bays=3 > pairs*2=2?
YES → Need new pair]
+ CreatePair[Create Leaf Pair 2
leaf3-DC1, leaf4-DC1]
+ CreateAccess[Create access3-DC1]
+ AssignBay[Assign Bay 3 → Pair 2]
+ AllocIPs[Allocate IPs]
+ CreateLinks[Create Interfaces]
+ ConfigBGP[Configure BGP
ASN: 65102]
+ end
+
+ subgraph Result["✅ New State - 3 Bays"]
+ NewDC[Datacenter: DC1
bays: 3
leaf_pairs: 2]
+ NewLP1[Leaf Pair 1
leaf1-2
ASN: 65101]
+ NewLP2[Leaf Pair 2
leaf3-4
ASN: 65102]
+ NewBay1[Bay 1 → access1]
+ NewBay2[Bay 2 → access2]
+ NewBay3[Bay 3 → access3]
+
+ NewDC --> NewLP1
+ NewDC --> NewLP2
+ NewLP1 --> NewBay1
+ NewLP1 --> NewBay2
+ NewLP2 --> NewBay3
+ end
+
+ Current --> AddBay
+ AddBay --> Check
+ Check --> CreatePair
+ CreatePair --> CreateAccess
+ CreateAccess --> AssignBay
+ AssignBay --> AllocIPs
+ AllocIPs --> CreateLinks
+ CreateLinks --> ConfigBGP
+ ConfigBGP --> Result
+
+ style AddBay fill:#fff59d
+ style Check fill:#ffccbc
+ style Result fill:#c8e6c9
+```
+
+## 8. Infrahub Generator Workflow
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Infrahub
+ participant Generator
+ participant Validator
+ participant GraphQL
+ participant Devices
+
+ User->>Infrahub: Create Datacenter Object
(name, dc_id, bays, etc.)
+ Infrahub->>Validator: Pre-Generation Validation
+ Validator-->>Infrahub: ✅ Valid Input
+
+ Infrahub->>Generator: Trigger Generator
+
+ Generator->>Generator: Compute Derived Values
(leaf_pairs, subnets, etc.)
+
+ loop For Each Layer
+ Generator->>Devices: Create Spine Devices
+ Generator->>Devices: Create Leaf Devices
+ Generator->>Devices: Create Access Devices
+ end
+
+ loop For Each Device
+ Generator->>Devices: Create Interfaces
+ Generator->>Devices: Allocate IP Addresses
+ Generator->>Devices: Generate BGP Config
+ end
+
+ Generator->>Validator: Post-Generation Validation
+ Validator-->>Generator: ✅ All Objects Valid
+
+ Generator->>GraphQL: Commit All Objects
+ GraphQL-->>Infrahub: Objects Created
+
+ Infrahub-->>User: ✅ Datacenter Generated
27 devices, 150 interfaces, 90 IPs
+```
+
+## 9. Configuration Generation Flow
+
+```mermaid
+graph TB
+ subgraph Infrahub["📊 Infrahub - Source of Truth"]
+ DeviceDB[(Device Objects
Interfaces
IPs
BGP Sessions)]
+ end
+
+ subgraph Generator["⚙️ Config Generator"]
+ Templates[Jinja2 Templates
by Device Role]
+ RenderEngine[Template Renderer]
+ end
+
+ subgraph Output["📄 Generated Configs"]
+ SpineConfig[spine1-DC1.cfg]
+ LeafConfig[leaf1-DC1.cfg]
+ AccessConfig[access1-DC1.cfg]
+ end
+
+ subgraph Deployment["🚀 Deployment"]
+ Validation[Config Validation]
+ Push[Push to Devices
via eAPI/NETCONF]
+ end
+
+ DeviceDB -->|Query Device Data| RenderEngine
+ Templates --> RenderEngine
+
+ RenderEngine --> SpineConfig
+ RenderEngine --> LeafConfig
+ RenderEngine --> AccessConfig
+
+ SpineConfig --> Validation
+ LeafConfig --> Validation
+ AccessConfig --> Validation
+
+ Validation --> Push
+
+ Push --> Devices[Physical Devices]
+
+ style Infrahub fill:#e1f5ff
+ style Generator fill:#fff9c4
+ style Output fill:#c8e6c9
+ style Deployment fill:#ffccbc
+```
+
+## 10. Complete Data Model Relationships
+
+```mermaid
+erDiagram
+ ORGANIZATION ||--o{ SITE : contains
+ SITE ||--o{ DATACENTER : contains
+ SITE ||--o{ IP_PREFIX : manages
+
+ DATACENTER ||--|{ SUBNET : generates
+ DATACENTER ||--|{ DEVICE : generates
+ DATACENTER ||--|{ MLAG_PAIR : generates
+
+ DEVICE ||--|{ INTERFACE : has
+ INTERFACE ||--o| IP_ADDRESS : assigned
+ INTERFACE ||--o| BGP_SESSION : endpoint
+
+ DEVICE }|--|| ROLE : has
+ DEVICE }o--o| MLAG_PAIR : member_of
+
+ MLAG_PAIR ||--|| DEVICE : primary
+ MLAG_PAIR ||--|| DEVICE : secondary
+
+ BGP_SESSION }o--|| INTERFACE : local
+ BGP_SESSION }o--|| INTERFACE : remote
+
+ SUBNET ||--|{ IP_ADDRESS : contains
+ IP_PREFIX ||--|{ SUBNET : parent
+
+ ORGANIZATION {
+ string name
+ }
+
+ SITE {
+ string name
+ string location
+ }
+
+ DATACENTER {
+ string name
+ int dc_id
+ int number_of_bays
+ int spine_count
+ string parent_subnet
+ }
+
+ DEVICE {
+ string hostname
+ string role
+ string mgmt_ip
+ }
+
+ INTERFACE {
+ string name
+ string type
+ int mtu
+ }
+
+ IP_ADDRESS {
+ string address
+ int prefix_length
+ }
+
+ BGP_SESSION {
+ int local_asn
+ int remote_asn
+ }
+```
\ No newline at end of file
diff --git a/infrahub/schema/01_organization.yml b/infrahub/schema/01_organization.yml
new file mode 100644
index 0000000..d029485
--- /dev/null
+++ b/infrahub/schema/01_organization.yml
@@ -0,0 +1,111 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: Organization
+ namespace: Core
+ label: "Organization"
+ icon: "mdi:domain"
+ include_in_menu: true
+ human_friendly_id: ["name__value"]
+ display_label: "{{ record.name__value }}"
+ order_by:
+ - name__value
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ description: "Organization name"
+ - name: description
+ kind: Text
+ optional: true
+ description: "Organization description"
+ - name: asn_base
+ kind: Number
+ optional: false
+ default_value: 65000
+ description: "Base ASN for BGP allocation (e.g., 65000)"
+ relationships:
+ - name: sites
+ peer: LocationSite
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Sites belonging to this organization"
+ - name: ip_namespaces
+ peer: IpamNamespace
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "IP namespaces managed by this organization"
+
+ - name: Site
+ namespace: Location
+ label: "Site"
+ icon: "mdi:office-building"
+ include_in_menu: true
+ menu_placement: "CoreOrganization"
+ human_friendly_id: ["name__value"]
+ display_label: "{{ record.name__value }}"
+ order_by:
+ - name__value
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ description: "Site name (e.g., Paris-DC)"
+ - name: description
+ kind: Text
+ optional: true
+ description: "Site description"
+ - name: location
+ kind: Text
+ optional: true
+ description: "Physical location (e.g., Paris, France)"
+ - name: facility_id
+ kind: Text
+ optional: true
+ description: "Facility identifier"
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "active"
+ choices:
+ - name: active
+ label: Active
+ description: Site is operational
+ color: "#7fbf7f"
+ - name: planned
+ label: Planned
+ description: Site is being planned
+ color: "#ffd966"
+ - name: maintenance
+ label: Maintenance
+ description: Site under maintenance
+ color: "#ff9999"
+ - name: decommissioned
+ label: Decommissioned
+ description: Site decommissioned
+ color: "#cccccc"
+ relationships:
+ - name: organization
+ peer: CoreOrganization
+ optional: false
+ cardinality: one
+ kind: Attribute
+ description: "Parent organization"
+ - name: datacenters
+ peer: InfraDatacenter
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Datacenters at this site"
+ - name: parent_prefix
+ peer: IpamIPPrefix
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Parent IP prefix for this site"
\ No newline at end of file
diff --git a/infrahub/schema/02_ipam.yml b/infrahub/schema/02_ipam.yml
new file mode 100644
index 0000000..113ebb0
--- /dev/null
+++ b/infrahub/schema/02_ipam.yml
@@ -0,0 +1,164 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: Namespace
+ namespace: Ipam
+ label: "IP Namespace"
+ icon: "mdi:ip-network"
+ include_in_menu: true
+ human_friendly_id: ["name__value"]
+ display_label: "{{ record.name__value }}"
+ order_by:
+ - name__value
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ description: "Namespace name (e.g., Global, Tenant1)"
+ - name: description
+ kind: Text
+ optional: true
+ description: "Namespace description"
+ relationships:
+ - name: organization
+ peer: CoreOrganization
+ optional: true
+ cardinality: one
+ kind: Attribute
+
+ - name: IPPrefix
+ namespace: Ipam
+ label: "IP Prefix"
+ icon: "mdi:ip-network-outline"
+ include_in_menu: true
+ human_friendly_id: ["prefix__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.prefix__value, record.description__value])) }}"
+ order_by:
+ - prefix__value
+ attributes:
+ - name: prefix
+ kind: IPNetwork
+ unique: true
+ optional: false
+ description: "IP Prefix (e.g., 10.1.0.0/24)"
+ - name: description
+ kind: Text
+ optional: true
+ description: "Prefix description"
+ - name: prefix_type
+ kind: Dropdown
+ optional: false
+ default_value: "pool"
+ choices:
+ - name: pool
+ label: Pool
+ description: IP address pool
+ - name: loopback
+ label: Loopback
+ description: Loopback addresses
+ - name: p2p
+ label: Point-to-Point
+ description: Point-to-point links
+ - name: management
+ label: Management
+ description: Management network
+ - name: tenant
+ label: Tenant
+ description: Tenant/VLAN network
+ - name: vtep
+ label: VTEP
+ description: VXLAN VTEP addresses
+ - name: mlag
+ label: MLAG
+ description: MLAG peer links
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "active"
+ choices:
+ - name: active
+ label: Active
+ - name: reserved
+ label: Reserved
+ - name: deprecated
+ label: Deprecated
+ relationships:
+ - name: namespace
+ peer: IpamNamespace
+ optional: false
+ cardinality: one
+ kind: Attribute
+ - name: parent
+ peer: IpamIPPrefix
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Parent prefix"
+ - name: children
+ peer: IpamIPPrefix
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Child prefixes"
+ - name: datacenter
+ peer: InfraDatacenter
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Associated datacenter"
+ - name: vrf
+ peer: NetworkVRF
+ optional: true
+ cardinality: one
+ kind: Attribute
+
+ - name: IPAddress
+ namespace: Ipam
+ label: "IP Address"
+ icon: "mdi:ip"
+ include_in_menu: true
+ human_friendly_id: ["address__value"]
+ display_label: "{{ record.address__value }}"
+ order_by:
+ - address__value
+ attributes:
+ - name: address
+ kind: IPHost
+ unique: true
+ optional: false
+ description: "IP Address with mask (e.g., 10.1.0.1/32)"
+ - name: description
+ kind: Text
+ optional: true
+ description: "IP address description"
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "active"
+ choices:
+ - name: active
+ label: Active
+ - name: reserved
+ label: Reserved
+ - name: deprecated
+ label: Deprecated
+ relationships:
+ - name: prefix
+ peer: IpamIPPrefix
+ optional: false
+ cardinality: one
+ kind: Attribute
+ - name: interface
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Interface using this IP"
+ - name: vrf
+ peer: NetworkVRF
+ optional: true
+ cardinality: one
+ kind: Attribute
\ No newline at end of file
diff --git a/infrahub/schema/03_datacenter.yml b/infrahub/schema/03_datacenter.yml
new file mode 100644
index 0000000..a070a74
--- /dev/null
+++ b/infrahub/schema/03_datacenter.yml
@@ -0,0 +1,211 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: Datacenter
+ namespace: Infra
+ label: "Datacenter"
+ icon: "mdi:server-network"
+ include_in_menu: true
+ menu_placement: "LocationSite"
+ human_friendly_id: ["name__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.name__value, record.dc_id__value])) }}"
+ order_by:
+ - dc_id__value
+ generate_template: false
+ description: "Datacenter - Generator object that creates fabric topology"
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ description: "Datacenter name (e.g., DC1, DC2)"
+ - name: dc_id
+ kind: Number
+ unique: true
+ optional: false
+ description: "Numeric datacenter ID for IP addressing (e.g., 1, 2)"
+ - name: description
+ kind: Text
+ optional: true
+ description: "Datacenter description"
+
+ - name: parent_subnet
+ kind: IPNetwork
+ optional: false
+ description: "Address space allocation"
+
+ # Topology Configuration
+ - name: number_of_bays
+ kind: Number
+ optional: false
+ default_value: 2
+ description: "Number of bays/racks (determines leaf count)"
+ - name: spine_count
+ kind: Number
+ optional: false
+ default_value: 3
+ description: "Number of spine switches"
+ - name: border_leaf_count
+ kind: Number
+ optional: false
+ default_value: 2
+ description: "Number of border leaf switches for DCI (0 if has_border_leafs=false)"
+
+ # BGP Configuration
+ - name: bgp_base_asn
+ kind: Number
+ optional: false
+ default_value: 65000
+ description: "Base ASN for BGP (e.g., 65000)"
+ - name: spine_asn
+ kind: Number
+ optional: true
+ description: "Spine ASN (auto-calculated: base + dc_id * 100)"
+
+ # MLAG Configuration
+ - name: mlag_domain_id
+ kind: Text
+ optional: false
+ default_value: "MLAG"
+ description: "MLAG domain identifier"
+
+ # Interface Configuration
+ - name: mtu
+ kind: Number
+ optional: false
+ default_value: 9214
+ description: "Default MTU for fabric interfaces"
+
+ # DCI Configuration (Optional)
+ - name: dci_enabled
+ kind: Boolean
+ optional: false
+ default_value: false
+ description: "Enable DCI connectivity (activates border leaf eth12). Default: false (eth12 shutdown)"
+ - name: dci_remote_dc_id
+ kind: Number
+ optional: true
+ description: "Remote datacenter ID for DCI peering (required when dci_enabled=true)"
+ - name: has_border_leafs
+ kind: Boolean
+ optional: false
+ default_value: true
+ description: "Include border leaf switches (set false if no DCI capability needed)"
+
+ # Computed/Derived Attributes (read-only, set by generator)
+ - name: leaf_pair_count
+ kind: Number
+ optional: true
+ read_only: true
+ description: "Computed: ceil(number_of_bays / 2)"
+ - name: total_leaf_count
+ kind: Number
+ optional: true
+ read_only: true
+ description: "Computed: leaf_pair_count * 2"
+ - name: total_access_count
+ kind: Number
+ optional: true
+ read_only: true
+ description: "Computed: number_of_bays"
+
+ # Status
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "planned"
+ choices:
+ - name: planned
+ label: Planned
+ color: "#ffd966"
+ - name: active
+ label: Active
+ color: "#7fbf7f"
+ - name: maintenance
+ label: Maintenance
+ color: "#ff9999"
+
+ relationships:
+ - name: site
+ peer: LocationSite
+ optional: false
+ cardinality: one
+ kind: Attribute
+ description: "Parent site"
+
+ - name: devices
+ peer: NetworkDevice
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "All devices in this datacenter (generated)"
+
+ - name: ip_prefixes
+ peer: IpamIPPrefix
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "IP prefixes for this datacenter (generated)"
+
+ - name: mlag_pairs
+ peer: NetworkMLAGDomain
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "MLAG pairs in this datacenter (generated)"
+
+ - name: Bay
+ namespace: Infra
+ label: "Bay/Rack"
+ icon: "mdi:server-outline"
+ include_in_menu: true
+ menu_placement: "InfraDatacenter"
+ human_friendly_id: ["name__value"]
+ display_label: "{{ record.name__value }}"
+ order_by:
+ - bay_id__value
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ description: "Bay name (e.g., Bay-1-DC1)"
+ - name: bay_id
+ kind: Number
+ optional: false
+ description: "Bay numeric ID"
+ - name: location
+ kind: Text
+ optional: true
+ description: "Physical location in datacenter"
+ - name: rack_units
+ kind: Number
+ optional: false
+ default_value: 42
+ description: "Available rack units (U)"
+ relationships:
+ - name: datacenter
+ peer: InfraDatacenter
+ optional: false
+ cardinality: one
+ kind: Parent
+ - name: access_switch
+ peer: NetworkDevice
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Access switch for this bay"
+ - name: leaf_pair
+ peer: NetworkMLAGDomain
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Leaf pair serving this bay"
+ - name: hosts
+ peer: NetworkDevice
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Host devices in this bay"
\ No newline at end of file
diff --git a/infrahub/schema/04_device.yml b/infrahub/schema/04_device.yml
new file mode 100644
index 0000000..5d9aa8d
--- /dev/null
+++ b/infrahub/schema/04_device.yml
@@ -0,0 +1,197 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: Device
+ namespace: Network
+ label: "Network Device"
+ icon: "mdi:router"
+ include_in_menu: true
+ human_friendly_id: ["hostname__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.hostname__value, record.role__value])) }}"
+ order_by:
+ - hostname__value
+ generate_template: false
+ attributes:
+ - name: hostname
+ kind: Text
+ unique: true
+ optional: false
+ description: "Device hostname (e.g., spine1-DC1)"
+
+ - name: description
+ kind: Text
+ optional: true
+ description: "Device description"
+
+ - name: role
+ kind: Dropdown
+ optional: false
+ choices:
+ - name: spine
+ label: Spine
+ description: "Spine switch (L3 core)"
+ color: "#ff6b6b"
+ - name: leaf
+ label: Leaf
+ description: "Leaf switch (ToR/Aggregation)"
+ color: "#4ecdc4"
+ - name: borderleaf
+ label: Border Leaf
+ description: "Border leaf (DCI gateway capable)"
+ color: "#95e1d3"
+ - name: access
+ label: Access
+ description: "Access switch (rack ToR)"
+ color: "#ffe66d"
+ - name: host
+ label: Host
+ description: "Host/Server device"
+ color: "#f1faee"
+
+ - name: platform
+ kind: Text
+ optional: false
+ default_value: "cEOS"
+ description: "Device platform (e.g., cEOS, vEOS, EOS)"
+
+ - name: eos_version
+ kind: Text
+ optional: true
+ description: "EOS software version"
+
+ - name: serial_number
+ kind: Text
+ optional: true
+ unique: true
+ description: "Device serial number"
+
+ - name: model
+ kind: Text
+ optional: true
+ description: "Device model (e.g., DCS-7050SX3-48YC8)"
+
+ - name: management_ip_template
+ kind: Text
+ optional: true
+ description: "Template for generating the management IP address"
+
+ - name: management_ip
+ kind: IPHost
+ optional: true
+ read_only: true
+ description: "Management IP address"
+
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "active"
+ choices:
+ - name: active
+ label: Active
+ color: "#7fbf7f"
+ - name: planned
+ label: Planned
+ color: "#ffd966"
+ - name: maintenance
+ label: Maintenance
+ color: "#ff9999"
+ - name: failed
+ label: Failed
+ color: "#ff0000"
+ - name: decommissioned
+ label: Decommissioned
+ color: "#cccccc"
+
+ # Role-specific attributes
+ - name: spine_id
+ kind: Number
+ optional: true
+ description: "Spine ID (1, 2, 3...)"
+
+ - name: leaf_id
+ kind: Number
+ optional: true
+ description: "Leaf ID (1, 2, 3, 4...)"
+
+ - name: mlag_side
+ kind: Dropdown
+ optional: true
+ choices:
+ - name: left
+ label: Left (Odd)
+ - name: right
+ label: Right (Even)
+ description: "MLAG pairing side (left=odd, right=even)"
+
+ relationships:
+ - name: datacenter
+ peer: InfraDatacenter
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Parent datacenter"
+
+ - name: bay
+ peer: InfraBay
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Bay location (for access/hosts)"
+
+ - name: site
+ peer: LocationSite
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Site location"
+
+ - name: interfaces
+ peer: NetworkInterface
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "Device interfaces"
+
+ - name: bgp_config
+ peer: NetworkBGPConfig
+ optional: true
+ cardinality: one
+ kind: Component
+ description: "BGP configuration"
+
+ - name: ospf_config
+ peer: NetworkOSPFConfig
+ optional: true
+ cardinality: one
+ kind: Component
+ description: "OSPF configuration"
+
+ - name: mlag_domain
+ peer: NetworkMLAGDomain
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "MLAG domain membership"
+
+ - name: evpn_config
+ peer: NetworkEVPNConfig
+ optional: true
+ cardinality: one
+ kind: Component
+ description: "EVPN configuration"
+
+ - name: vlans
+ peer: NetworkVLAN
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "VLANs configured on device"
+
+ - name: vrfs
+ peer: NetworkVRF
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "VRFs configured on device"
\ No newline at end of file
diff --git a/infrahub/schema/05_interfaces.yml b/infrahub/schema/05_interfaces.yml
new file mode 100644
index 0000000..f62ce61
--- /dev/null
+++ b/infrahub/schema/05_interfaces.yml
@@ -0,0 +1,211 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: Interface
+ namespace: Network
+ label: "Interface"
+ icon: "mdi:ethernet"
+ include_in_menu: true
+ human_friendly_id: ["device__hostname__value", "name__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.name__value, record.description__value])) }}"
+ order_by:
+ - device__hostname__value
+ - name__value
+ uniqueness_constraints:
+ - ["device", "name__value"]
+ attributes:
+ - name: name
+ kind: Text
+ optional: false
+ description: "Interface name (e.g., Ethernet1, Loopback0)"
+
+ - name: description
+ kind: Text
+ optional: true
+ description: "Interface description"
+
+ - name: interface_type
+ kind: Dropdown
+ optional: false
+ default_value: "ethernet"
+ choices:
+ - name: ethernet
+ label: Ethernet
+ description: "Physical Ethernet interface"
+ - name: loopback
+ label: Loopback
+ description: "Loopback interface"
+ - name: port_channel
+ label: Port-Channel
+ description: "Link aggregation group"
+ - name: vlan
+ label: VLAN (SVI)
+ description: "VLAN interface"
+ - name: vxlan
+ label: VXLAN
+ description: "VXLAN tunnel interface"
+ - name: management
+ label: Management
+ description: "Management interface"
+
+ - name: enabled
+ kind: Boolean
+ optional: false
+ default_value: true
+ description: "Interface administrative status"
+
+ - name: mtu
+ kind: Number
+ optional: true
+ default_value: 9214
+ description: "Interface MTU"
+
+ - name: speed
+ kind: Dropdown
+ optional: true
+ choices:
+ - name: 1g
+ label: 1 Gbps
+ - name: 10g
+ label: 10 Gbps
+ - name: 25g
+ label: 25 Gbps
+ - name: 40g
+ label: 40 Gbps
+ - name: 100g
+ label: 100 Gbps
+ description: "Interface speed"
+
+ # Layer 2 Attributes
+ - name: switchport_mode
+ kind: Dropdown
+ optional: true
+ choices:
+ - name: access
+ label: Access
+ - name: trunk
+ label: Trunk
+ - name: routed
+ label: Routed (no switchport)
+ description: "Switchport mode"
+
+ - name: trunk_groups
+ kind: Text
+ optional: true
+ description: "Trunk groups (comma-separated)"
+
+ # Layer 3 Attributes
+ - name: ip_unnumbered
+ kind: Boolean
+ optional: false
+ default_value: false
+ description: "Use IP unnumbered"
+
+ # Port-Channel Attributes
+ - name: channel_id
+ kind: Number
+ optional: true
+ description: "Port-channel ID"
+
+ - name: lacp_mode
+ kind: Dropdown
+ optional: true
+ choices:
+ - name: active
+ label: Active
+ - name: passive
+ label: Passive
+ - name: on
+ label: On (no LACP)
+ description: "LACP mode for port-channel"
+
+ # Loopback Attributes
+ - name: loopback_id
+ kind: Number
+ optional: true
+ description: "Loopback interface ID (0, 1, etc.)"
+
+ - name: loopback_purpose
+ kind: Dropdown
+ optional: true
+ choices:
+ - name: router_id
+ label: Router ID (Loopback0)
+ - name: vtep
+ label: VTEP (Loopback1)
+ - name: other
+ label: Other
+ description: "Loopback purpose"
+
+ relationships:
+ - name: device
+ peer: NetworkDevice
+ optional: false
+ cardinality: one
+ kind: Parent
+ description: "Parent device"
+
+ - name: ip_addresses
+ peer: IpamIPAddress
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "IP addresses assigned to interface"
+
+ - name: unnumbered_source
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Source interface for IP unnumbered"
+
+ - name: allowed_vlans
+ peer: NetworkVLAN
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "VLANs allowed on trunk"
+
+ - name: port_channel
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Parent port-channel"
+
+ - name: member_interfaces
+ peer: NetworkInterface
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Member interfaces (for port-channel)"
+
+ - name: connected_to
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Remote interface connection"
+
+ - name: mlag_config
+ peer: NetworkMLAGInterface
+ optional: true
+ cardinality: one
+ kind: Component
+ description: "MLAG interface configuration"
+
+ - name: bgp_sessions
+ peer: NetworkBGPNeighbor
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "BGP sessions on this interface"
+
+ - name: ospf_config
+ peer: NetworkOSPFInterface
+ optional: true
+ cardinality: one
+ kind: Component
+ description: "OSPF interface configuration"
\ No newline at end of file
diff --git a/infrahub/schema/06_vlan_vrfs.yml b/infrahub/schema/06_vlan_vrfs.yml
new file mode 100644
index 0000000..fe447e2
--- /dev/null
+++ b/infrahub/schema/06_vlan_vrfs.yml
@@ -0,0 +1,146 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: VLAN
+ namespace: Network
+ label: "VLAN"
+ icon: "mdi:lan"
+ include_in_menu: true
+ human_friendly_id: ["vlan_id__value", "name__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.vlan_id__value, record.name__value])) }}"
+ order_by:
+ - vlan_id__value
+ attributes:
+ - name: vlan_id
+ kind: Number
+ optional: false
+ description: "VLAN ID (1-4094)"
+
+ - name: name
+ kind: Text
+ optional: false
+ description: "VLAN name"
+
+ - name: description
+ kind: Text
+ optional: true
+ description: "VLAN description"
+
+ - name: trunk_groups
+ kind: Text
+ optional: true
+ description: "Trunk groups (comma-separated, e.g., MLAGPEER)"
+
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "active"
+ choices:
+ - name: active
+ label: Active
+ - name: suspended
+ label: Suspended
+ description: "VLAN status"
+
+ relationships:
+ - name: devices
+ peer: NetworkDevice
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Devices with this VLAN"
+
+ - name: interfaces
+ peer: NetworkInterface
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Interfaces with this VLAN"
+
+ - name: svi
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "SVI interface for this VLAN"
+
+ - name: vrf
+ peer: NetworkVRF
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "VRF for this VLAN"
+
+ - name: vxlan_tunnel
+ peer: NetworkVXLANTunnel
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "VXLAN tunnel mapping"
+
+ - name: VRF
+ namespace: Network
+ label: "VRF"
+ icon: "mdi:router-network"
+ include_in_menu: true
+ human_friendly_id: ["name__value"]
+ display_label: "{{ record.name__value }}"
+ order_by:
+ - name__value
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ description: "VRF name (e.g., default, tenant1)"
+
+ - name: description
+ kind: Text
+ optional: true
+ description: "VRF description"
+
+ - name: route_distinguisher
+ kind: Text
+ optional: true
+ description: "Route Distinguisher (e.g., 65001:100)"
+
+ - name: rt_import
+ kind: Text
+ optional: true
+ description: "Route Target Import (comma-separated)"
+
+ - name: rt_export
+ kind: Text
+ optional: true
+ description: "Route Target Export (comma-separated)"
+
+ relationships:
+ - name: devices
+ peer: NetworkDevice
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Devices with this VRF"
+
+ - name: interfaces
+ peer: NetworkInterface
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Interfaces in this VRF"
+
+ - name: ip_prefixes
+ peer: IpamIPPrefix
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "IP prefixes in this VRF"
+
+ - name: vlans
+ peer: NetworkVLAN
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "VLANs in this VRF"
\ No newline at end of file
diff --git a/infrahub/schema/07_bgp.yml b/infrahub/schema/07_bgp.yml
new file mode 100644
index 0000000..7e24e50
--- /dev/null
+++ b/infrahub/schema/07_bgp.yml
@@ -0,0 +1,290 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: BGPConfig
+ namespace: Network
+ label: "BGP Configuration"
+ icon: "mdi:routes"
+ include_in_menu: true
+ human_friendly_id: ["device__hostname__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.device__hostname__value, record.asn__value])) }}"
+ order_by:
+ - device__hostname__value
+ attributes:
+ - name: asn
+ kind: Number
+ optional: false
+ description: "BGP AS Number"
+
+ - name: router_id
+ kind: IPHost
+ optional: false
+ description: "BGP Router ID (usually Loopback0)"
+
+ - name: maximum_paths
+ kind: Number
+ optional: false
+ default_value: 3
+ description: "Maximum paths for ECMP"
+
+ - name: distance_external
+ kind: Number
+ optional: false
+ default_value: 20
+ description: "Administrative distance for eBGP routes"
+
+ - name: distance_internal
+ kind: Number
+ optional: false
+ default_value: 200
+ description: "Administrative distance for iBGP routes"
+
+ - name: default_ipv4_unicast
+ kind: Boolean
+ optional: false
+ default_value: false
+ description: "Enable default IPv4 unicast address family"
+
+ - name: ebgp_admin_distance
+ kind: Number
+ optional: false
+ default_value: 200
+ description: "eBGP admin distance override"
+
+ relationships:
+ - name: device
+ peer: NetworkDevice
+ optional: false
+ cardinality: one
+ kind: Parent
+ description: "Parent device"
+
+ - name: peer_groups
+ peer: NetworkBGPPeerGroup
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "BGP peer groups"
+
+ - name: neighbors
+ peer: NetworkBGPNeighbor
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "BGP neighbors"
+
+ - name: address_families
+ peer: NetworkBGPAddressFamily
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "BGP address families"
+
+ - name: route_maps
+ peer: NetworkRouteMap
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Route maps used in BGP"
+
+ - name: prefix_lists
+ peer: NetworkPrefixList
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Prefix lists used in BGP"
+
+ - name: BGPPeerGroup
+ namespace: Network
+ label: "BGP Peer Group"
+ icon: "mdi:account-group"
+ include_in_menu: false
+ human_friendly_id: ["bgp_config__device__hostname__value", "name__value"]
+ display_label: "{{ record.name__value }}"
+ order_by:
+ - name__value
+ attributes:
+ - name: name
+ kind: Text
+ optional: false
+ description: "Peer group name (e.g., SPINE_Underlay)"
+
+ - name: remote_asn
+ kind: Number
+ optional: true
+ description: "Remote AS number (for eBGP)"
+
+ - name: send_community
+ kind: Boolean
+ optional: false
+ default_value: true
+ description: "Send community attributes"
+
+ - name: maximum_routes
+ kind: Number
+ optional: false
+ default_value: 12000
+ description: "Maximum routes from peers"
+
+ - name: peer_type
+ kind: Dropdown
+ optional: false
+ default_value: "ebgp"
+ choices:
+ - name: ebgp
+ label: eBGP
+ - name: ibgp
+ label: iBGP
+ description: "Peer type"
+
+ - name: next_hop_self
+ kind: Boolean
+ optional: false
+ default_value: false
+ description: "Set next-hop self for routes"
+
+ relationships:
+ - name: bgp_config
+ peer: NetworkBGPConfig
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: update_source
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Update source interface"
+
+ - name: neighbors
+ peer: NetworkBGPNeighbor
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Neighbors in this peer group"
+
+ - name: BGPNeighbor
+ namespace: Network
+ label: "BGP Neighbor"
+ icon: "mdi:account-network"
+ include_in_menu: false
+ human_friendly_id: ["bgp_config__device__hostname__value", "neighbor_ip__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.neighbor_ip__value, record.description__value])) }}"
+ order_by:
+ - neighbor_ip__value
+ attributes:
+ - name: neighbor_ip
+ kind: IPHost
+ optional: false
+ description: "Neighbor IP address"
+
+ - name: description
+ kind: Text
+ optional: true
+ description: "Neighbor description"
+
+ - name: enabled
+ kind: Boolean
+ optional: false
+ default_value: true
+ description: "Neighbor enabled"
+
+ - name: peer_type
+ kind: Dropdown
+ optional: false
+ default_value: "ebgp"
+ choices:
+ - name: ebgp
+ label: eBGP
+ - name: ibgp
+ label: iBGP
+ description: "Peer type"
+
+ relationships:
+ - name: bgp_config
+ peer: NetworkBGPConfig
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: peer_group
+ peer: NetworkBGPPeerGroup
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Peer group membership"
+
+ - name: local_interface
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Local interface for session"
+
+ - name: remote_device
+ peer: NetworkDevice
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Remote device (if known)"
+
+ - name: BGPAddressFamily
+ namespace: Network
+ label: "BGP Address Family"
+ icon: "mdi:family-tree"
+ include_in_menu: false
+ human_friendly_id: ["bgp_config__device__hostname__value", "afi__value", "safi__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.afi__value, record.safi__value])) }}"
+ attributes:
+ - name: afi
+ kind: Dropdown
+ optional: false
+ choices:
+ - name: ipv4
+ label: IPv4
+ - name: ipv6
+ label: IPv6
+ - name: l2vpn
+ label: L2VPN
+ description: "Address Family Identifier"
+
+ - name: safi
+ kind: Dropdown
+ optional: false
+ choices:
+ - name: unicast
+ label: Unicast
+ - name: multicast
+ label: Multicast
+ - name: evpn
+ label: EVPN
+ description: "Sub-Address Family Identifier"
+
+ - name: activated
+ kind: Boolean
+ optional: false
+ default_value: true
+ description: "Address family activated"
+
+ - name: redistribute_connected
+ kind: Boolean
+ optional: false
+ default_value: false
+ description: "Redistribute connected routes"
+
+ relationships:
+ - name: bgp_config
+ peer: NetworkBGPConfig
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: route_map
+ peer: NetworkRouteMap
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Route map for redistribution"
\ No newline at end of file
diff --git a/infrahub/schema/08_mlag_evpn_vxlan.yml b/infrahub/schema/08_mlag_evpn_vxlan.yml
new file mode 100644
index 0000000..3e6be27
--- /dev/null
+++ b/infrahub/schema/08_mlag_evpn_vxlan.yml
@@ -0,0 +1,151 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: MLAGDomain
+ namespace: Network
+ label: "MLAG Domain"
+ icon: "mdi:link-variant"
+ include_in_menu: true
+ human_friendly_id: ["domain_id__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.domain_id__value, record.status__value])) }}"
+ order_by:
+ - domain_id__value
+ attributes:
+ - name: domain_id
+ kind: Text
+ unique: true
+ optional: false
+ description: "MLAG domain identifier (e.g., MLAG-leaf1-2)"
+
+ - name: local_interface
+ kind: Text
+ optional: false
+ default_value: "Vlan4094"
+ description: "Local interface for MLAG peer link"
+
+ - name: peer_interface
+ kind: Text
+ optional: false
+ default_value: "Vlan4094"
+ description: "Peer interface for MLAG peer link"
+
+ - name: peer_address
+ kind: IPHost
+ optional: false
+ description: "Peer IP address for MLAG link"
+
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "active"
+ choices:
+ - name: active
+ label: Active
+ - name: inactive
+ label: Inactive
+ description: "MLAG domain status"
+
+ relationships:
+ - name: devices
+ peer: NetworkDevice
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Devices in this MLAG domain"
+
+ - name: interfaces
+ peer: NetworkMLAGInterface
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "MLAG-enabled interfaces"
+
+ - name: MLAGInterface
+ namespace: Network
+ label: "MLAG Interface"
+ icon: "mdi:ethernet-plus"
+ include_in_menu: false
+ human_friendly_id: ["interface__device__hostname__value", "interface__name__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.interface__device__hostname__value, record.interface__name__value])) }}"
+ attributes:
+ - name: mlag_id
+ kind: Number
+ optional: false
+ description: "MLAG interface ID"
+
+ relationships:
+ - name: mlag_domain
+ peer: NetworkMLAGDomain
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: interface
+ peer: NetworkInterface
+ optional: false
+ cardinality: one
+ kind: Attribute
+ description: "Physical interface"
+
+ - name: EVPNConfig
+ namespace: Network
+ label: "EVPN Configuration"
+ icon: "mdi:wan"
+ include_in_menu: false
+ human_friendly_id: ["device__hostname__value"]
+ display_label: "{{ record.device__hostname__value }}"
+ attributes:
+ - name: vni_auto
+ kind: Boolean
+ optional: false
+ default_value: true
+ description: "Automatically generate VNIs"
+
+ relationships:
+ - name: device
+ peer: NetworkDevice
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: VXLANTunnel
+ namespace: Network
+ label: "VXLAN Tunnel"
+ icon: "mdi:tunnel"
+ include_in_menu: true
+ human_friendly_id: ["name__value"]
+ display_label: "{{ record.name__value }}"
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ default_value: "Vxlan1"
+ description: "VXLAN tunnel interface name"
+
+ - name: source_ip
+ kind: IPHost
+ optional: false
+ description: "Source IP for VXLAN tunnel (Loopback1)"
+
+ - name: udp_port
+ kind: Number
+ optional: false
+ default_value: 4789
+ description: "VXLAN UDP port"
+
+ relationships:
+ - name: device
+ peer: NetworkDevice
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: vlans
+ peer: NetworkVLAN
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "VLANs mapped to this VXLAN tunnel"
\ No newline at end of file
diff --git a/infrahub/schema/09_routing_policies.yml b/infrahub/schema/09_routing_policies.yml
new file mode 100644
index 0000000..00047b9
--- /dev/null
+++ b/infrahub/schema/09_routing_policies.yml
@@ -0,0 +1,239 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: RouteMap
+ namespace: Network
+ label: "Route Map"
+ icon: "mdi:map-marker-path"
+ include_in_menu: true
+ human_friendly_id: ["name__value"]
+ display_label: "{{ record.name__value }}"
+ order_by:
+ - name__value
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ description: "Route map name (e.g., LOOPBACK)"
+
+ - name: sequence
+ kind: Number
+ optional: false
+ default_value: 10
+ description: "Sequence number"
+
+ - name: action
+ kind: Dropdown
+ optional: false
+ default_value: "permit"
+ choices:
+ - name: permit
+ label: Permit
+ - name: deny
+ label: Deny
+ description: "Action (permit/deny)"
+
+ relationships:
+ - name: devices
+ peer: NetworkDevice
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Devices using this route map"
+
+ - name: prefix_lists
+ peer: NetworkPrefixList
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Prefix lists matched by this route map"
+
+ - name: PrefixList
+ namespace: Network
+ label: "Prefix List"
+ icon: "mdi:format-list-numbered"
+ include_in_menu: true
+ human_friendly_id: ["name__value"]
+ display_label: "{{ record.name__value }}"
+ order_by:
+ - name__value
+ attributes:
+ - name: name
+ kind: Text
+ unique: true
+ optional: false
+ description: "Prefix list name (e.g., LOOPBACK)"
+
+ - name: sequence
+ kind: Number
+ optional: false
+ default_value: 10
+ description: "Sequence number"
+
+ - name: action
+ kind: Dropdown
+ optional: false
+ default_value: "permit"
+ choices:
+ - name: permit
+ label: Permit
+ - name: deny
+ label: Deny
+ description: "Action (permit/deny)"
+
+ - name: prefix
+ kind: IPNetwork
+ optional: false
+ description: "IP prefix to match"
+
+ - name: match_type
+ kind: Dropdown
+ optional: false
+ default_value: "exact"
+ choices:
+ - name: exact
+ label: Exact Match
+ - name: ge
+ label: Greater or Equal
+ - name: le
+ label: Less or Equal
+ description: "Match type"
+
+ - name: greater_equal
+ kind: Number
+ optional: true
+ description: "Greater or equal prefix length"
+
+ - name: less_equal
+ kind: Number
+ optional: true
+ description: "Less or equal prefix length"
+
+ relationships:
+ - name: devices
+ peer: NetworkDevice
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Devices using this prefix list"
+
+ - name: OSPFConfig
+ namespace: Network
+ label: "OSPF Configuration"
+ icon: "mdi:router"
+ include_in_menu: false
+ human_friendly_id: ["device__hostname__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.device__hostname__value, record.process_id__value])) }}"
+ attributes:
+ - name: process_id
+ kind: Number
+ optional: false
+ default_value: 100
+ description: "OSPF process ID"
+
+ - name: router_id
+ kind: IPHost
+ optional: false
+ description: "OSPF router ID"
+
+ - name: max_paths
+ kind: Number
+ optional: false
+ default_value: 3
+ description: "Maximum paths for ECMP"
+
+ relationships:
+ - name: device
+ peer: NetworkDevice
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: areas
+ peer: NetworkOSPFArea
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "OSPF areas"
+
+ - name: OSPFArea
+ namespace: Network
+ label: "OSPF Area"
+ icon: "mdi:circle-outline"
+ include_in_menu: false
+ human_friendly_id: ["ospf_config__device__hostname__value", "area_id__value"]
+ display_label: "{{ record.area_id__value }}"
+ attributes:
+ - name: area_id
+ kind: Text
+ optional: false
+ default_value: "0.0.0.0"
+ description: "OSPF area ID (e.g., 0.0.0.0)"
+
+ - name: area_type
+ kind: Dropdown
+ optional: false
+ default_value: "standard"
+ choices:
+ - name: standard
+ label: Standard
+ - name: stub
+ label: Stub
+ - name: nssa
+ label: NSSA
+ description: "Area type"
+
+ relationships:
+ - name: ospf_config
+ peer: NetworkOSPFConfig
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: interfaces
+ peer: NetworkOSPFInterface
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "Interfaces in this area"
+
+ - name: OSPFInterface
+ namespace: Network
+ label: "OSPF Interface"
+ icon: "mdi:ethernet"
+ include_in_menu: false
+ human_friendly_id: ["interface__device__hostname__value", "interface__name__value"]
+ display_label: "{{ record.interface__name__value }}"
+ attributes:
+ - name: network_type
+ kind: Dropdown
+ optional: false
+ default_value: "point-to-point"
+ choices:
+ - name: point-to-point
+ label: Point-to-Point
+ - name: broadcast
+ label: Broadcast
+ description: "OSPF network type"
+
+ - name: cost
+ kind: Number
+ optional: true
+ description: "OSPF cost"
+
+ relationships:
+ - name: area
+ peer: NetworkOSPFArea
+ optional: false
+ cardinality: one
+ kind: Parent
+
+ - name: interface
+ peer: NetworkInterface
+ optional: false
+ cardinality: one
+ kind: Attribute
+ description: "Physical interface"
\ No newline at end of file
diff --git a/infrahub/schema/10_dci.yml b/infrahub/schema/10_dci.yml
new file mode 100644
index 0000000..b94b7dd
--- /dev/null
+++ b/infrahub/schema/10_dci.yml
@@ -0,0 +1,219 @@
+# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
+---
+version: "1.0"
+
+nodes:
+ - name: DCISwitch
+ namespace: Network
+ label: "DCI Interconnect Switch"
+ icon: "mdi:transit-connection-variant"
+ include_in_menu: true
+ human_friendly_id: ["hostname__value"]
+ display_label: "{{ record.hostname__value }}"
+ order_by:
+ - hostname__value
+ description: "DCI switch connects multiple datacenters - NOT auto-generated, manually configured"
+ attributes:
+ - name: hostname
+ kind: Text
+ unique: true
+ optional: false
+ description: "DCI switch hostname (e.g., DCI)"
+
+ - name: description
+ kind: Text
+ optional: true
+ default_value: "DCI Interconnect Switch"
+ description: "DCI switch description"
+
+ - name: platform
+ kind: Text
+ optional: false
+ default_value: "cEOS"
+ description: "Device platform"
+
+ - name: eos_version
+ kind: Text
+ optional: true
+ description: "EOS software version"
+
+ - name: serial_number
+ kind: Text
+ optional: true
+ unique: true
+ description: "Device serial number"
+
+ - name: management_ip
+ kind: IPHost
+ optional: true
+ description: "Management IP address (e.g., 10.255.0.253)"
+
+ - name: loopback0_ip
+ kind: IPHost
+ optional: false
+ description: "Loopback0 IP for BGP router-id (e.g., 10.253.0.1/32)"
+
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "active"
+ choices:
+ - name: active
+ label: Active
+ color: "#7fbf7f"
+ - name: planned
+ label: Planned
+ color: "#ffd966"
+ - name: maintenance
+ label: Maintenance
+ color: "#ff9999"
+ - name: decommissioned
+ label: Decommissioned
+ color: "#cccccc"
+
+ relationships:
+ - name: organization
+ peer: CoreOrganization
+ optional: false
+ cardinality: one
+ kind: Attribute
+ description: "Parent organization"
+
+ - name: connected_datacenters
+ peer: InfraDatacenter
+ optional: true
+ cardinality: many
+ kind: Generic
+ description: "Datacenters interconnected by this DCI switch"
+
+ - name: interfaces
+ peer: NetworkInterface
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "DCI switch interfaces"
+
+ - name: bgp_config
+ peer: NetworkBGPConfig
+ optional: true
+ cardinality: one
+ kind: Component
+ description: "BGP configuration for DCI"
+
+ - name: dci_connections
+ peer: NetworkDCIConnection
+ optional: true
+ cardinality: many
+ kind: Component
+ description: "DCI connections to border leafs"
+
+ - name: DCIConnection
+ namespace: Network
+ label: "DCI Connection"
+ icon: "mdi:cable-data"
+ include_in_menu: true
+ menu_placement: "NetworkDCISwitch"
+ human_friendly_id: ["dci_switch__hostname__value", "border_leaf__hostname__value"]
+ display_label: "{{ ' - '.join(filter(None, [record.dci_switch__hostname__value, record.border_leaf__hostname__value, record.status__value])) }}"
+ order_by:
+ - dci_switch__hostname__value
+ description: "Represents a P2P connection between DCI switch and a border leaf"
+ attributes:
+ - name: connection_name
+ kind: Text
+ optional: true
+ description: "Connection identifier (e.g., DCI-to-borderleaf1-DC1)"
+
+ - name: status
+ kind: Dropdown
+ optional: false
+ default_value: "shutdown"
+ choices:
+ - name: active
+ label: Active
+ description: "Connection is enabled and operational"
+ color: "#7fbf7f"
+ - name: shutdown
+ label: Shutdown
+ description: "Connection administratively disabled"
+ color: "#cccccc"
+ - name: maintenance
+ label: Maintenance
+ description: "Connection under maintenance"
+ color: "#ff9999"
+ description: "Connection operational status (shutdown until dci_enabled=true)"
+
+ - name: dci_interface_name
+ kind: Text
+ optional: false
+ description: "DCI switch interface (e.g., Ethernet1)"
+
+ - name: border_interface_name
+ kind: Text
+ optional: false
+ default_value: "Ethernet12"
+ description: "Border leaf interface (always Ethernet12)"
+
+ - name: dci_ip
+ kind: IPHost
+ optional: false
+ description: "DCI side IP address (e.g., 10.254.0.1/31)"
+
+ - name: border_ip
+ kind: IPHost
+ optional: false
+ description: "Border leaf side IP address (e.g., 10.254.0.0/31)"
+
+ - name: subnet
+ kind: IPNetwork
+ optional: false
+ description: "P2P subnet (e.g., 10.254.0.0/31)"
+
+ - name: mtu
+ kind: Number
+ optional: false
+ default_value: 9214
+ description: "Interface MTU"
+
+ relationships:
+ - name: dci_switch
+ peer: NetworkDCISwitch
+ optional: false
+ cardinality: one
+ kind: Parent
+ description: "Parent DCI switch"
+
+ - name: border_leaf
+ peer: NetworkDevice
+ optional: false
+ cardinality: one
+ kind: Attribute
+ description: "Connected border leaf device"
+
+ - name: datacenter
+ peer: InfraDatacenter
+ optional: false
+ cardinality: one
+ kind: Attribute
+ description: "Datacenter of the border leaf"
+
+ - name: dci_interface
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "DCI switch interface object"
+
+ - name: border_interface
+ peer: NetworkInterface
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "Border leaf interface object (eth12)"
+
+ - name: bgp_session
+ peer: NetworkBGPNeighbor
+ optional: true
+ cardinality: one
+ kind: Attribute
+ description: "BGP session for this connection"
\ No newline at end of file
diff --git a/schema/Device.yaml b/schema/Device.yaml
deleted file mode 100644
index 9bb3706..0000000
--- a/schema/Device.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-# yaml-language-server $schema=https://schema.infrahub.app/infrahub/schema/latest.json
-version: "1.0"
-nodes:
- - name: Device
- namespace: Infra