Quick Start - OVN Topology Generation
IMMEDIATE ACTIONS (follow these steps in order):
Detect Cluster: Find the OVN-Kubernetes cluster kubeconfig
Run: scripts/detect-cluster.sh 2>/dev/null
The script discovers OVN-Kubernetes clusters:
- Scans all kubeconfig files: current KUBECONFIG env, ~/.kube/kind-config, ~/ovn.conf, ~/.kube/config
- Tests ALL contexts in each kubeconfig (not just current-context)
- Returns parseable list to stdout:
index|kubeconfig|cluster_name|node_count|namespace
- Diagnostics go to stderr
- Exit code: 0=success, 1=no clusters found
How to handle the output:
The script returns pipe-delimited lines to stdout, one per cluster found, e.g.:
1|/home/user/.kube/kind-config|kind-ovn|3|ovn-kubernetes
2|/home/user/.kube/config|prod-cluster|12|openshift-ovn-kubernetes
Decision logic:
- If one cluster found → automatically use it (extract kubeconfig path from column 2)
- If multiple clusters found → show the list to user and ask them to choose by number
- After selection, extract the kubeconfig path from column 2 of the chosen line
- Store the selected kubeconfig path in variable
KC for use in subsequent steps
Example output format parsing:
- Column 1: Index number (for user selection)
- Column 2: Kubeconfig file path (this is what you need for
$KC)
- Column 3: Cluster display name
- Column 4: Number of nodes
- Column 5: OVN namespace name
Important: Parse the output using standard text processing. The exact implementation is up to you - use whatever approach works best (awk, Python, inline parsing, etc.).
Check Permissions: Verify user's Kubernetes access level and inform about write permissions
Run: scripts/check_permissions.py "$KC"
The script returns:
- Exit 0: Read-only access or user confirmed → proceed
- Exit 1: Error or user cancelled → stop
- Exit 2: Write permissions detected → AI must ask user for confirmation
When exit code 2 is returned:
- Parse the stdout to get the list of write permissions
- Display the permissions clearly to the user using a formatted message
- Explain that:
- This skill performs ONLY read-only operations
- No cluster modifications will be made
- The warning is for transparency about their access level
- List read-only operations: kubectl get, kubectl exec (ovn-nbctl list), local file writes
- List forbidden operations: kubectl create/delete/patch, ovn-nbctl modifications
- Ask the user explicitly: "You have cluster admin permissions. This command will only perform read-only operations. Do you want to proceed?"
- If user says yes → continue, if no → stop
Example of proper user communication:
⚠️ WARNING: Write Permissions Detected
Your kubeconfig has cluster admin permissions:
• Delete pods, deployments, services
• Create and modify resources
• Full cluster access
📋 IMPORTANT:
This command will ONLY perform read-only operations:
✅ kubectl get (pods, nodes)
✅ kubectl exec (to run read-only ovn-nbctl list commands)
✅ Local file writes (topology diagram)
Operations that will NEVER be performed:
❌ kubectl create/delete/patch/apply
❌ ovn-nbctl modifications
❌ Any cluster state changes
Do you want to proceed with read-only topology generation?
Security Note: This step ensures informed consent. The user must be explicitly aware that their cluster admin credentials are accessible to the AI agent (acting on their behalf), even though only read-only operations will be performed. This transparency is critical for security and trust.
Check Output File: Ask user if ovn-topology-diagram.md exists:
- (1) Overwrite, (2) Custom path, (3) Timestamp, (4) Cancel
Create Private Temp Directory: Create a private temporary directory using mkdtemp and use it for all temporary files.
TMPDIR=$(mktemp -d)
Collect OVN Data: Get full topology data from the cluster
Run: scripts/collect_ovn_data.py "$KC" "$TMPDIR"
Detail files written to $TMPDIR:
ovn_switches_detail.txt - node|uuid|name|other_config
ovn_routers_detail.txt - node|uuid|name|external_ids|options
ovn_lsps_detail.txt - node|name|addresses|type|options
ovn_lrps_detail.txt - node|name|mac|networks|options
ovn_pods_detail.txt - namespace|name|ip|node
Analyze Placement: Determine per-node vs cluster-wide components
Run: scripts/analyze_placement.py "$TMPDIR"
Placement results written to $TMPDIR:
ovn_switch_placement.txt - name|placement (per-node|cluster-wide|cluster-wide-visual)
ovn_router_placement.txt - name|placement (per-node|cluster-wide|cluster-wide-visual)
Generate Diagram: Create Mermaid graph BT diagram
- Read
$TMPDIR/ovn_switch_placement.txt to determine where each switch goes
- Read
$TMPDIR/ovn_router_placement.txt to determine where each router goes
- Read detail files directly (ovn_switches_detail.txt, ovn_routers_detail.txt, etc.)
- Skip UUID column when parsing switches/routers detail files
- If placement is
per-node → put inside node subgraph
- If placement is
cluster-wide or cluster-wide-visual → put outside subgraphs
Save & Report: Write diagram to file, show summary, clean up temporary files
CRITICAL RULES:
- ❌ NO codebase searching for IPs/MACs
- ❌ NO synthetic/example data
- ❌ NO inline multi-line bash (use helper scripts)
- ❌ NO direct kubectl commands (must use helper scripts only)
- ✅ Use helper scripts for all kubectl interactions and architecture discovery
- ✅ For helper scripts only: If kubectl is required, use
KUBECONFIG="$KC" kubectl --kubeconfig="$KC"
- ✅ SECURITY: Create private temp directory with
TMPDIR=$(mktemp -d) - never use /tmp directly
- ✅ Temporary files use
$TMPDIR (private directory created with mkdtemp)
- ✅ Clean up temporary files when done:
rm -rf "$TMPDIR"
Safety & Security Guarantees
🔒 Read-Only Operations
This skill performs ONLY read-only operations against your Kubernetes cluster. No cluster state is modified.
Allowed Operations:
- ✅
kubectl get - Query resources
- ✅
kubectl exec ... ovn-nbctl list - Query OVN database (read-only)
- ✅ Local file writes (temporary files in
$TMPDIR, output diagram)
Forbidden Operations (NEVER used):
- ❌
kubectl create/apply/delete/patch - No resource modifications
- ❌
kubectl scale/drain/cordon - No cluster operations
- ❌
ovn-nbctl create/set/add/remove/destroy - No OVN modifications
- ❌ No pod restarts or service disruptions
Privacy Consideration: The generated diagram contains network topology information. Control sharing based on your organization's security policies and data classification requirements.
Architecture Concepts
Interconnect Mode (Distributed NBDB)
In interconnect mode, each node runs its own NBDB with local copies of components:
Per-Node Components (different UUIDs on each node):
ovn_cluster_router - Each node has its own cluster router instance
join switch - Each node has its own join switch instance
transit_switch - Each node has its own transit switch instance
- Node switches (e.g.,
ovn-control-plane, ovn-worker)
- External switches (e.g.,
ext_ovn-control-plane)
- Gateway routers (e.g.,
GR_ovn-control-plane)
Visualization Overrides:
transit_switch: PER-NODE in reality → shown CLUSTER-WIDE for visualization clarity
join: PER-NODE → kept PER-NODE (no override)
Helper Scripts
All helper scripts are in the scripts/ directory.
| Script |
Purpose |
Input |
Output |
| detect-cluster.sh |
Find OVN cluster kubeconfig across all contexts. Scans multiple kubeconfig files and all their contexts. Returns parseable list. |
None |
Parseable list to stdout: index|kubeconfig|cluster|nodes|namespace. Exit: 0=success, 1=none found |
| check_permissions.py |
Check user permissions and warn if write access detected. |
KUBECONFIG path |
Exit: 0=proceed, 1=cancelled/error, 2=write perms (needs user confirmation) |
| collect_ovn_data.py |
Data collector: Queries each node for all data, with graceful degradation (continues on node failures). Writes detail files. |
KUBECONFIG path, TMPDIR |
Detail files: ovn_switches_detail.txt, ovn_routers_detail.txt, ovn_lsps_detail.txt, ovn_lrps_detail.txt, ovn_pods_detail.txt |
| analyze_placement.py |
Placement analyzer: Analyzes UUID patterns from detail files to determine per-node vs cluster-wide placement. |
TMPDIR (reads detail files) |
Placement files: ovn_switch_placement.txt, ovn_router_placement.txt |
Diagram Generation Rules
Structure
graph BT
subgraph node1["<b style='color:black'>Node: name (node_ip)</b>"]
direction BT
%% LAYER 1 (Bottom): Pods and Management Ports
POD_example["Pod: pod-name<br/>Namespace: ns<br/>IP: x.x.x.x"]
MGMT["Management Port: k8s-node<br/>IP: x.x.x.x"]
%% LAYER 2: Pod LSPs
LSP_pod["LSP: namespace_pod-name<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: x.x.x.x"]
LSP_mgmt["LSP: k8s-node<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: x.x.x.x"]
%% LAYER 3: Node Switch
LS_node["Logical Switch: node-name<br/>Subnet: x.x.x.x/24"]
%% LAYER 4: Node Switch LSPs
LSP_stor["LSP: stor-node<br/>Type: router"]
%% LAYER 5: Cluster Router
LR_cluster["Logical Router: ovn_cluster_router"]
LRP_rtos["LRP: rtos-node<br/>MAC: xx:xx<br/>IP: x.x.x.x/24"]
LRP_rtoj["LRP: rtoj-ovn_cluster_router<br/>IP: x.x.x.x/16"]
LRP_rtots["LRP: rtots-node<br/>IP: x.x.x.x/16"]
%% LAYER 6: Join Switch
LS_join["Logical Switch: join"]
LSP_jtor_cr["LSP: jtor-ovn_cluster_router<br/>Type: router"]
LSP_jtor_gr["LSP: jtor-GR_node<br/>Type: router"]
%% LAYER 7: Gateway Router
LR_gr["Gateway Router: GR_node"]
LRP_rtoj_gr["LRP: rtoj-GR_node<br/>IP: x.x.x.x/16"]
LRP_rtoe["LRP: rtoe-GR_node<br/>IP: x.x.x.x/24"]
%% LAYER 8: External Switch
LS_ext["Logical Switch: ext_node"]
LSP_etor["LSP: etor-GR_node<br/>Type: router"]
LSP_breth0["LSP: breth0_node<br/>Type: localnet"]
%% LAYER 9 (Top): External Network
EXT_NET["External Network<br/>Physical bridge: breth0<br/>Node IP: x.x.x.x"]
%% Connections (bottom-to-top flow)
POD_example --> LSP_pod --> LS_node
MGMT --> LSP_mgmt --> LS_node
LS_node --> LSP_stor
LSP_stor -.->|peer| LRP_rtos --> LR_cluster
LR_cluster --> LRP_rtoj -.->|peer| LSP_jtor_cr --> LS_join
LS_join --> LSP_jtor_gr -.->|peer| LRP_rtoj_gr --> LR_gr
LR_gr --> LRP_rtoe -.->|peer| LSP_etor --> LS_ext
LS_ext --> LSP_breth0 -.->|physical| EXT_NET
LR_cluster --> LRP_rtots
end
%% Cluster-wide components (AFTER nodes to appear on top)
%% Only include components with placement=cluster-wide or cluster-wide-visual
%% Example: transit_switch in interconnect mode
LS_cluster_component["Component Name<br/>Details"]
%% Connections from nodes to cluster-wide components
LRP_from_node -.->|connects to| LSP_cluster_port --> LS_cluster_component
Key Requirements
Graph Direction: Always graph BT (bottom-to-top)
Component Placement: Determined by $TMPDIR/ovn_*_placement.txt
per-node → INSIDE node subgraph
cluster-wide or cluster-wide-visual → OUTSIDE all subgraphs, defined AFTER all node subgraphs
- CRITICAL: Define ALL cluster-wide components AFTER all nodes to position them at the TOP
- Prevents connection lines from overlapping with node subgraphs
- Applies to ANY component with
cluster-wide or cluster-wide-visual placement
Node Subgraphs: Each physical node gets a subgraph with direction BT
- Node Ordering: ALWAYS order nodes as: control-plane node first, then worker nodes sorted alphabetically by name
- Title format:
"<b style='color:black'>Node: {node_name} ({external_ip})</b>"
- Get external IP from
rtoe-GR_{node} router port network field
- Use different background colors for each node for visual distinction
- CRITICAL: Define components in BOTTOM-TO-TOP order (matches packet flow):
- Bottom Layer: Pods and Management Ports (traffic originates here)
- Layer 2: Pod LSPs
- Layer 3: Node Switch (where pods connect)
- Layer 4: Node Switch LSPs (stor, breth0)
- Layer 5: Cluster Router + Router Ports (rtos, rtoj, rtots)
- Layer 6: Join Switch + Join LSPs
- Layer 7: Gateway Router + Router Ports (rtoj, rtoe)
- Layer 8: External Switch + External LSPs (etor, breth0)
- Top Layer: External Network (physical bridge)
Pod Representation:
- CRITICAL: Show ALL pods from
$TMPDIR/ovn_pods_detail.txt as SEPARATE entities
- DO NOT discover pods from LSPs - many pods (host-network) don't have individual LSPs
- Pod format:
POD_{id}["Pod: {name}<br/>Namespace: {ns}<br/>IP: {ip}"]
- Connect pods to their respective LSPs:
- Host-network pods (IP == node IP):
POD_{id} --> MGMT_{node} --> LSP_k8s_{node}
- Pod-network pods (IP in 10.244.x.x):
POD_{id} --> LSP_{namespace}_{podname}
Physical Network Layer:
- Show explicit external network entities per node
- Format:
EXT_NET_{node}["External Network<br/>Physical bridge: breth0<br/>Node IP: {external_ip}"]
- Connect localnet LSPs to external network:
LSP_breth0_{node} -.->|physical| EXT_NET_{node}
Colors (apply using classDef with color:#000 for black text):
- Pods:
fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000
- Switches:
fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000
- Routers:
fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
- LSPs (ALL types):
fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000
- LRPs:
fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,color:#000
- External Network:
fill:#e0e0e0,stroke:#424242,stroke-width:2px,color:#000
- Management Ports (MGMT entities only, not LSPs):
fill:#fff8e1,stroke:#f57c00,stroke-width:2px,color:#000
- Node Subgraph Backgrounds (use
style statements, NOT classDef): Apply at END of diagram after all class assignments. Rotate through these 3 colors:
- Node 1 (index 0):
style node1 fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,stroke-dasharray: 5 5,color:#000 (green)
- Node 2 (index 1):
style node2 fill:#e1f5fe,stroke:#0277bd,stroke-width:3px,stroke-dasharray: 5 5,color:#000 (blue)
- Node 3 (index 2):
style node3 fill:#fff3e0,stroke:#ef6c00,stroke-width:3px,stroke-dasharray: 5 5,color:#000 (orange)
- Node 4+ repeats: Use index % 3 to rotate colors (0=green, 1=blue, 2=orange)
Required Info:
- Pods: Name, Namespace, IP
- Switches: Name, Subnet (from other_config), Gateway (optional)
- LSPs: Name, MAC, IP (or Type + Router Port for router ports)
- LRPs: Name, MAC, Network (IP/CIDR)
- Routers: Name, Description
- External Network: Physical bridge name (breth0), Node IP
Connections:
- Solid arrows: Layer connections
- Dashed arrows with
|peer|: Peer port relationships
Pod Discovery
CRITICAL: Discover pods from $TMPDIR/ovn_pods_detail.txt, NOT from LSPs
The file $TMPDIR/ovn_pods_detail.txt contains ALL running pods in the cluster (populated by collect_ovn_data.py).
Format: namespace|pod_name|pod_ip|node_name
Pod-to-LSP Mapping Logic:
Read ALL pods from $TMPDIR/ovn_pods_detail.txt
For each pod, determine which LSP it connects to:
a. Host-Network Pods (pod IP == node IP, e.g., 10.89.0.10):
- Connect to management port LSP:
k8s-{node}
- These pods share the management port's IP address
- MUST be included in diagram despite not having individual LSPs
b. Pod-Network Pods (pod IP in pod network range, e.g., 10.244.x.x):
- Connect to individual LSP:
{namespace}_{pod-name-with-hash}
- LSP name format:
kube-system_coredns-674b8bbfcf-qhfrq
- Filter LSPs where
type="" (empty string)
- Extract MAC and IP from LSP addresses field
Diagram representation:
POD_id["Pod: {name}<br/>Namespace: {ns}<br/>IP: {ip}"]
POD_id --> LSP_id
Special LSPs (NOT pods, treat as infrastructure):
k8s-{node}: Management port LSP (multiple host-network pods connect to this)
stor-{node}: Router port (type="router")
breth0_{node}: LocalNet port (type="localnet")
jtor-*, etor-*, tstor-*: Router ports (type="router")
Example: Control Plane Node with Host-Network Pods
%% 8 host-network pods sharing management port
POD_etcd["Pod: etcd-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]
POD_apiserver["Pod: kube-apiserver-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]
POD_controller["Pod: kube-controller-manager-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]
POD_scheduler["Pod: kube-scheduler-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]
POD_ovnkube_cp["Pod: ovnkube-control-plane-ovn-control-plane<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]
POD_ovnkube_id["Pod: ovnkube-identity-ovn-control-plane<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]
POD_ovnkube_node["Pod: ovnkube-node-xyz<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]
POD_ovs_node["Pod: ovs-node-xyz<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]
%% Management port LSP (shared by all host-network pods)
MGMT_cp["Management Port: k8s-ovn-control-plane<br/>IP: 10.89.0.10"]
LSP_mgmt["LSP: k8s-ovn-control-plane<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: 10.244.0.2"]
%% All host-network pods connect to same management port
POD_etcd --> MGMT_cp
POD_apiserver --> MGMT_cp
POD_controller --> MGMT_cp
POD_scheduler --> MGMT_cp
POD_ovnkube_cp --> MGMT_cp
POD_ovnkube_id --> MGMT_cp
POD_ovnkube_node --> MGMT_cp
POD_ovs_node --> MGMT_cp
MGMT_cp --> LSP_mgmt --> LS_node
Final Steps
- Generate complete Mermaid diagram following structure above
- Save to file chosen by user
- Show summary: nodes, switches, routers, ports, mode
- Clean up temporary directory:
rm -rf "$TMPDIR"
- Tell user to open file in IDE to view rendered diagram
1---2name: generating-ovn-topology3description: Generates and displays OVN-Kubernetes network topology diagrams showing logical switches, routers, ports with IP/MAC addresses in Mermaid format4---56# Quick Start - OVN Topology Generation78**IMMEDIATE ACTIONS** (follow these steps in order):9101. **Detect Cluster**: Find the OVN-Kubernetes cluster kubeconfig1112 Run: `scripts/detect-cluster.sh 2>/dev/null`1314 The script discovers OVN-Kubernetes clusters:15 - Scans all kubeconfig files: current KUBECONFIG env, ~/.kube/kind-config, ~/ovn.conf, ~/.kube/config16 - Tests ALL contexts in each kubeconfig (not just current-context)17 - Returns parseable list to stdout: `index|kubeconfig|cluster_name|node_count|namespace`18 - Diagnostics go to stderr19 - Exit code: 0=success, 1=no clusters found2021 **How to handle the output:**2223 The script returns pipe-delimited lines to stdout, one per cluster found, e.g.:24 ```text25 1|/home/user/.kube/kind-config|kind-ovn|3|ovn-kubernetes26 2|/home/user/.kube/config|prod-cluster|12|openshift-ovn-kubernetes27 ```2829 **Decision logic:**30 - If **one cluster** found → automatically use it (extract kubeconfig path from column 2)31 - If **multiple clusters** found → show the list to user and ask them to choose by number32 - After selection, extract the kubeconfig path from column 2 of the chosen line33 - Store the selected kubeconfig path in variable `KC` for use in subsequent steps3435 **Example output format parsing:**36 - Column 1: Index number (for user selection)37 - Column 2: Kubeconfig file path (this is what you need for `$KC`)38 - Column 3: Cluster display name39 - Column 4: Number of nodes40 - Column 5: OVN namespace name4142 **Important**: Parse the output using standard text processing. The exact implementation is up to you - use whatever approach works best (awk, Python, inline parsing, etc.).43442. **Check Permissions**: Verify user's Kubernetes access level and inform about write permissions4546 Run: `scripts/check_permissions.py "$KC"`4748 The script returns:49 - **Exit 0**: Read-only access or user confirmed → proceed50 - **Exit 1**: Error or user cancelled → stop51 - **Exit 2**: Write permissions detected → AI must ask user for confirmation5253 **When exit code 2 is returned:**54 1. Parse the stdout to get the list of write permissions55 2. Display the permissions clearly to the user using a formatted message56 3. Explain that:57 - This skill performs ONLY read-only operations58 - No cluster modifications will be made59 - The warning is for transparency about their access level60 - List read-only operations: kubectl get, kubectl exec (ovn-nbctl list), local file writes61 - List forbidden operations: kubectl create/delete/patch, ovn-nbctl modifications62 4. **Ask the user explicitly**: "You have cluster admin permissions. This command will only perform read-only operations. Do you want to proceed?"63 5. If user says yes → continue, if no → stop6465 **Example of proper user communication:**66 ```text67 ⚠️ WARNING: Write Permissions Detected6869 Your kubeconfig has cluster admin permissions:70 • Delete pods, deployments, services71 • Create and modify resources72 • Full cluster access7374 📋 IMPORTANT:75 This command will ONLY perform read-only operations:76 ✅ kubectl get (pods, nodes)77 ✅ kubectl exec (to run read-only ovn-nbctl list commands)78 ✅ Local file writes (topology diagram)7980 Operations that will NEVER be performed:81 ❌ kubectl create/delete/patch/apply82 ❌ ovn-nbctl modifications83 ❌ Any cluster state changes8485 Do you want to proceed with read-only topology generation?86 ```8788 **Security Note**: This step ensures informed consent. The user must be explicitly aware that their cluster admin credentials are accessible to the AI agent (acting on their behalf), even though only read-only operations will be performed. This transparency is critical for security and trust.89903. **Check Output File**: Ask user if `ovn-topology-diagram.md` exists:91 - (1) Overwrite, (2) Custom path, (3) Timestamp, (4) Cancel92934. **Create Private Temp Directory**: Create a private temporary directory using `mkdtemp` and use it for all temporary files.9495 ```bash96 TMPDIR=$(mktemp -d)97 ```98995. **Collect OVN Data**: Get full topology data from the cluster100101 Run: `scripts/collect_ovn_data.py "$KC" "$TMPDIR"`102103 Detail files written to `$TMPDIR`:104 - `ovn_switches_detail.txt` - node|uuid|name|other_config105 - `ovn_routers_detail.txt` - node|uuid|name|external_ids|options106 - `ovn_lsps_detail.txt` - node|name|addresses|type|options107 - `ovn_lrps_detail.txt` - node|name|mac|networks|options108 - `ovn_pods_detail.txt` - namespace|name|ip|node1091106. **Analyze Placement**: Determine per-node vs cluster-wide components111112 Run: `scripts/analyze_placement.py "$TMPDIR"`113114 Placement results written to `$TMPDIR`:115 - `ovn_switch_placement.txt` - name|placement (per-node|cluster-wide|cluster-wide-visual)116 - `ovn_router_placement.txt` - name|placement (per-node|cluster-wide|cluster-wide-visual)1171187. **Generate Diagram**: Create Mermaid `graph BT` diagram119 - Read `$TMPDIR/ovn_switch_placement.txt` to determine where each switch goes120 - Read `$TMPDIR/ovn_router_placement.txt` to determine where each router goes121 - Read detail files directly (ovn_switches_detail.txt, ovn_routers_detail.txt, etc.)122 - Skip UUID column when parsing switches/routers detail files123 - If placement is `per-node` → put inside node subgraph124 - If placement is `cluster-wide` or `cluster-wide-visual` → put outside subgraphs1251268. **Save & Report**: Write diagram to file, show summary, clean up temporary files127128**CRITICAL RULES**:129- ❌ NO codebase searching for IPs/MACs130- ❌ NO synthetic/example data131- ❌ NO inline multi-line bash (use helper scripts)132- ❌ NO direct kubectl commands (must use helper scripts only)133- ✅ Use helper scripts for all kubectl interactions and architecture discovery134- ✅ **For helper scripts only**: If kubectl is required, use `KUBECONFIG="$KC" kubectl --kubeconfig="$KC"`135- ✅ **SECURITY**: Create private temp directory with `TMPDIR=$(mktemp -d)` - never use `/tmp` directly136- ✅ Temporary files use `$TMPDIR` (private directory created with mkdtemp)137- ✅ Clean up temporary files when done: `rm -rf "$TMPDIR"`138139## Safety & Security Guarantees140141### 🔒 Read-Only Operations142143This skill performs **ONLY read-only operations** against your Kubernetes cluster. No cluster state is modified.144145**Allowed Operations:**146- ✅ `kubectl get` - Query resources147- ✅ `kubectl exec ... ovn-nbctl list` - Query OVN database (read-only)148- ✅ Local file writes (temporary files in `$TMPDIR`, output diagram)149150**Forbidden Operations (NEVER used):**151- ❌ `kubectl create/apply/delete/patch` - No resource modifications152- ❌ `kubectl scale/drain/cordon` - No cluster operations153- ❌ `ovn-nbctl create/set/add/remove/destroy` - No OVN modifications154- ❌ No pod restarts or service disruptions155156**Privacy Consideration**: The generated diagram contains network topology information. Control sharing based on your organization's security policies and data classification requirements.157158---159160# Architecture Concepts161162## Interconnect Mode (Distributed NBDB)163164In **interconnect mode**, each node runs its own NBDB with local copies of components:165166**Per-Node Components** (different UUIDs on each node):167- `ovn_cluster_router` - Each node has its own cluster router instance168- `join` switch - Each node has its own join switch instance169- `transit_switch` - Each node has its own transit switch instance170- Node switches (e.g., `ovn-control-plane`, `ovn-worker`)171- External switches (e.g., `ext_ovn-control-plane`)172- Gateway routers (e.g., `GR_ovn-control-plane`)173174**Visualization Overrides**:175- `transit_switch`: PER-NODE in reality → shown CLUSTER-WIDE for visualization clarity176- `join`: PER-NODE → kept PER-NODE (no override)177178## Helper Scripts179180All helper scripts are in the `scripts/` directory.181182| Script | Purpose | Input | Output |183|--------|---------|-------|--------|184| [detect-cluster.sh](scripts/detect-cluster.sh) | Find OVN cluster kubeconfig across all contexts. Scans multiple kubeconfig files and all their contexts. Returns parseable list. | None | Parseable list to stdout: `index\|kubeconfig\|cluster\|nodes\|namespace`. Exit: 0=success, 1=none found |185| [check_permissions.py](scripts/check_permissions.py) | Check user permissions and warn if write access detected. | KUBECONFIG path | Exit: 0=proceed, 1=cancelled/error, 2=write perms (needs user confirmation) |186| [collect_ovn_data.py](scripts/collect_ovn_data.py) | **Data collector**: Queries each node for all data, with **graceful degradation** (continues on node failures). Writes detail files. | KUBECONFIG path, TMPDIR | Detail files: `ovn_switches_detail.txt`, `ovn_routers_detail.txt`, `ovn_lsps_detail.txt`, `ovn_lrps_detail.txt`, `ovn_pods_detail.txt` |187| [analyze_placement.py](scripts/analyze_placement.py) | **Placement analyzer**: Analyzes UUID patterns from detail files to determine per-node vs cluster-wide placement. | TMPDIR (reads detail files) | Placement files: `ovn_switch_placement.txt`, `ovn_router_placement.txt` |188189---190191# Diagram Generation Rules192193## Structure194195```mermaid196graph BT197 subgraph node1["<b style='color:black'>Node: name (node_ip)</b>"]198 direction BT199200 %% LAYER 1 (Bottom): Pods and Management Ports201 POD_example["Pod: pod-name<br/>Namespace: ns<br/>IP: x.x.x.x"]202 MGMT["Management Port: k8s-node<br/>IP: x.x.x.x"]203204 %% LAYER 2: Pod LSPs205 LSP_pod["LSP: namespace_pod-name<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: x.x.x.x"]206 LSP_mgmt["LSP: k8s-node<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: x.x.x.x"]207208 %% LAYER 3: Node Switch209 LS_node["Logical Switch: node-name<br/>Subnet: x.x.x.x/24"]210211 %% LAYER 4: Node Switch LSPs212 LSP_stor["LSP: stor-node<br/>Type: router"]213214 %% LAYER 5: Cluster Router215 LR_cluster["Logical Router: ovn_cluster_router"]216 LRP_rtos["LRP: rtos-node<br/>MAC: xx:xx<br/>IP: x.x.x.x/24"]217 LRP_rtoj["LRP: rtoj-ovn_cluster_router<br/>IP: x.x.x.x/16"]218 LRP_rtots["LRP: rtots-node<br/>IP: x.x.x.x/16"]219220 %% LAYER 6: Join Switch221 LS_join["Logical Switch: join"]222 LSP_jtor_cr["LSP: jtor-ovn_cluster_router<br/>Type: router"]223 LSP_jtor_gr["LSP: jtor-GR_node<br/>Type: router"]224225 %% LAYER 7: Gateway Router226 LR_gr["Gateway Router: GR_node"]227 LRP_rtoj_gr["LRP: rtoj-GR_node<br/>IP: x.x.x.x/16"]228 LRP_rtoe["LRP: rtoe-GR_node<br/>IP: x.x.x.x/24"]229230 %% LAYER 8: External Switch231 LS_ext["Logical Switch: ext_node"]232 LSP_etor["LSP: etor-GR_node<br/>Type: router"]233 LSP_breth0["LSP: breth0_node<br/>Type: localnet"]234235 %% LAYER 9 (Top): External Network236 EXT_NET["External Network<br/>Physical bridge: breth0<br/>Node IP: x.x.x.x"]237238 %% Connections (bottom-to-top flow)239 POD_example --> LSP_pod --> LS_node240 MGMT --> LSP_mgmt --> LS_node241 LS_node --> LSP_stor242 LSP_stor -.->|peer| LRP_rtos --> LR_cluster243 LR_cluster --> LRP_rtoj -.->|peer| LSP_jtor_cr --> LS_join244 LS_join --> LSP_jtor_gr -.->|peer| LRP_rtoj_gr --> LR_gr245 LR_gr --> LRP_rtoe -.->|peer| LSP_etor --> LS_ext246 LS_ext --> LSP_breth0 -.->|physical| EXT_NET247 LR_cluster --> LRP_rtots248 end249250 %% Cluster-wide components (AFTER nodes to appear on top)251 %% Only include components with placement=cluster-wide or cluster-wide-visual252 %% Example: transit_switch in interconnect mode253 LS_cluster_component["Component Name<br/>Details"]254255 %% Connections from nodes to cluster-wide components256 LRP_from_node -.->|connects to| LSP_cluster_port --> LS_cluster_component257```258259## Key Requirements2602611. **Graph Direction**: Always `graph BT` (bottom-to-top)2622632. **Component Placement**: Determined by `$TMPDIR/ovn_*_placement.txt`264 - `per-node` → INSIDE node subgraph265 - `cluster-wide` or `cluster-wide-visual` → OUTSIDE all subgraphs, **defined AFTER all node subgraphs**266 - **CRITICAL**: Define ALL cluster-wide components AFTER all nodes to position them at the TOP267 - Prevents connection lines from overlapping with node subgraphs268 - Applies to ANY component with `cluster-wide` or `cluster-wide-visual` placement2692703. **Node Subgraphs**: Each physical node gets a subgraph with `direction BT`271 - **Node Ordering**: ALWAYS order nodes as: control-plane node first, then worker nodes sorted alphabetically by name272 - Title format: `"<b style='color:black'>Node: {node_name} ({external_ip})</b>"`273 - Get external IP from `rtoe-GR_{node}` router port network field274 - Use different background colors for each node for visual distinction275 - **CRITICAL: Define components in BOTTOM-TO-TOP order** (matches packet flow):276 1. **Bottom Layer**: Pods and Management Ports (traffic originates here)277 2. **Layer 2**: Pod LSPs278 3. **Layer 3**: Node Switch (where pods connect)279 4. **Layer 4**: Node Switch LSPs (stor, breth0)280 5. **Layer 5**: Cluster Router + Router Ports (rtos, rtoj, rtots)281 6. **Layer 6**: Join Switch + Join LSPs282 7. **Layer 7**: Gateway Router + Router Ports (rtoj, rtoe)283 8. **Layer 8**: External Switch + External LSPs (etor, breth0)284 9. **Top Layer**: External Network (physical bridge)2852864. **Pod Representation**:287 - **CRITICAL**: Show ALL pods from `$TMPDIR/ovn_pods_detail.txt` as SEPARATE entities288 - **DO NOT** discover pods from LSPs - many pods (host-network) don't have individual LSPs289 - Pod format: `POD_{id}["Pod: {name}<br/>Namespace: {ns}<br/>IP: {ip}"]`290 - Connect pods to their respective LSPs:291 - **Host-network pods** (IP == node IP): `POD_{id} --> MGMT_{node} --> LSP_k8s_{node}`292 - **Pod-network pods** (IP in 10.244.x.x): `POD_{id} --> LSP_{namespace}_{podname}`2932945. **Physical Network Layer**:295 - Show explicit external network entities per node296 - Format: `EXT_NET_{node}["External Network<br/>Physical bridge: breth0<br/>Node IP: {external_ip}"]`297 - Connect localnet LSPs to external network: `LSP_breth0_{node} -.->|physical| EXT_NET_{node}`2982996. **Colors** (apply using classDef with **color:#000** for black text):300 - Pods: `fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000`301 - Switches: `fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000`302 - Routers: `fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000`303 - LSPs (ALL types): `fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000`304 - LRPs: `fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,color:#000`305 - External Network: `fill:#e0e0e0,stroke:#424242,stroke-width:2px,color:#000`306 - Management Ports (MGMT entities only, not LSPs): `fill:#fff8e1,stroke:#f57c00,stroke-width:2px,color:#000`307 - **Node Subgraph Backgrounds** (use `style` statements, NOT classDef): Apply at END of diagram after all class assignments. Rotate through these 3 colors:308 - Node 1 (index 0): `style node1 fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,stroke-dasharray: 5 5,color:#000` (green)309 - Node 2 (index 1): `style node2 fill:#e1f5fe,stroke:#0277bd,stroke-width:3px,stroke-dasharray: 5 5,color:#000` (blue)310 - Node 3 (index 2): `style node3 fill:#fff3e0,stroke:#ef6c00,stroke-width:3px,stroke-dasharray: 5 5,color:#000` (orange)311 - Node 4+ repeats: Use index % 3 to rotate colors (0=green, 1=blue, 2=orange)3123137. **Required Info**:314 - Pods: Name, Namespace, IP315 - Switches: Name, Subnet (from other_config), Gateway (optional)316 - LSPs: Name, MAC, IP (or Type + Router Port for router ports)317 - LRPs: Name, MAC, Network (IP/CIDR)318 - Routers: Name, Description319 - External Network: Physical bridge name (breth0), Node IP3203218. **Connections**:322 - Solid arrows: Layer connections323 - Dashed arrows with `|peer|`: Peer port relationships324325## Pod Discovery326327**CRITICAL: Discover pods from `$TMPDIR/ovn_pods_detail.txt`, NOT from LSPs**328329The file `$TMPDIR/ovn_pods_detail.txt` contains ALL running pods in the cluster (populated by collect_ovn_data.py).330Format: `namespace|pod_name|pod_ip|node_name`331332**Pod-to-LSP Mapping Logic:**3333341. **Read ALL pods** from `$TMPDIR/ovn_pods_detail.txt`3352. **For each pod**, determine which LSP it connects to:336337 a. **Host-Network Pods** (pod IP == node IP, e.g., 10.89.0.10):338 - **Connect to management port LSP**: `k8s-{node}`339 - These pods share the management port's IP address340 - **MUST be included in diagram** despite not having individual LSPs341342 b. **Pod-Network Pods** (pod IP in pod network range, e.g., 10.244.x.x):343 - **Connect to individual LSP**: `{namespace}_{pod-name-with-hash}`344 - LSP name format: `kube-system_coredns-674b8bbfcf-qhfrq`345 - Filter LSPs where `type=""` (empty string)346 - Extract MAC and IP from LSP addresses field3473483. **Diagram representation**:349 ```mermaid350 POD_id["Pod: {name}<br/>Namespace: {ns}<br/>IP: {ip}"]351 POD_id --> LSP_id352 ```353354**Special LSPs (NOT pods, treat as infrastructure):**355- `k8s-{node}`: Management port LSP (multiple host-network pods connect to this)356- `stor-{node}`: Router port (type="router")357- `breth0_{node}`: LocalNet port (type="localnet")358- `jtor-*`, `etor-*`, `tstor-*`: Router ports (type="router")359360### Example: Control Plane Node with Host-Network Pods361362```mermaid363%% 8 host-network pods sharing management port364POD_etcd["Pod: etcd-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]365POD_apiserver["Pod: kube-apiserver-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]366POD_controller["Pod: kube-controller-manager-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]367POD_scheduler["Pod: kube-scheduler-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]368POD_ovnkube_cp["Pod: ovnkube-control-plane-ovn-control-plane<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]369POD_ovnkube_id["Pod: ovnkube-identity-ovn-control-plane<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]370POD_ovnkube_node["Pod: ovnkube-node-xyz<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]371POD_ovs_node["Pod: ovs-node-xyz<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]372373%% Management port LSP (shared by all host-network pods)374MGMT_cp["Management Port: k8s-ovn-control-plane<br/>IP: 10.89.0.10"]375LSP_mgmt["LSP: k8s-ovn-control-plane<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: 10.244.0.2"]376377%% All host-network pods connect to same management port378POD_etcd --> MGMT_cp379POD_apiserver --> MGMT_cp380POD_controller --> MGMT_cp381POD_scheduler --> MGMT_cp382POD_ovnkube_cp --> MGMT_cp383POD_ovnkube_id --> MGMT_cp384POD_ovnkube_node --> MGMT_cp385POD_ovs_node --> MGMT_cp386387MGMT_cp --> LSP_mgmt --> LS_node388```389390---391392# Final Steps3933941. Generate complete Mermaid diagram following structure above3952. Save to file chosen by user3963. Show summary: nodes, switches, routers, ports, mode3974. Clean up temporary directory:398 ```bash399 rm -rf "$TMPDIR"400 ```4015. Tell user to open file in IDE to view rendered diagram