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