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 format4tools: [Bash, Read, Write]5---67# Quick Start - OVN Topology Generation89**IMMEDIATE ACTIONS** (follow these steps in order):10111. **Detect Cluster**: Find the OVN-Kubernetes cluster kubeconfig1213 Run: `scripts/detect-cluster.sh 2>/dev/null`1415 The script discovers OVN-Kubernetes clusters:16 - Scans all kubeconfig files: current KUBECONFIG env, ~/.kube/kind-config, ~/ovn.conf, ~/.kube/config17 - Tests ALL contexts in each kubeconfig (not just current-context)18 - Returns parseable list to stdout: `index|kubeconfig|cluster_name|node_count|namespace`19 - Diagnostics go to stderr20 - Exit code: 0=success, 1=no clusters found2122 **How to handle the output:**2324 The script returns pipe-delimited lines to stdout, one per cluster found, e.g.:25 ```text26 1|/home/user/.kube/kind-config|kind-ovn|3|ovn-kubernetes27 2|/home/user/.kube/config|prod-cluster|12|openshift-ovn-kubernetes28 ```2930 **Decision logic:**31 - If **one cluster** found → automatically use it (extract kubeconfig path from column 2)32 - If **multiple clusters** found → show the list to user and ask them to choose by number33 - After selection, extract the kubeconfig path from column 2 of the chosen line34 - Store the selected kubeconfig path in variable `KC` for use in subsequent steps3536 **Example output format parsing:**37 - Column 1: Index number (for user selection)38 - Column 2: Kubeconfig file path (this is what you need for `$KC`)39 - Column 3: Cluster display name40 - Column 4: Number of nodes41 - Column 5: OVN namespace name4243 **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.).44452. **Check Permissions**: Verify user's Kubernetes access level and inform about write permissions4647 Run: `scripts/check_permissions.py "$KC"`4849 The script returns:50 - **Exit 0**: Read-only access or user confirmed → proceed51 - **Exit 1**: Error or user cancelled → stop52 - **Exit 2**: Write permissions detected → AI must ask user for confirmation5354 **When exit code 2 is returned:**55 1. Parse the stdout to get the list of write permissions56 2. Display the permissions clearly to the user using a formatted message57 3. Explain that:58 - This skill performs ONLY read-only operations59 - No cluster modifications will be made60 - The warning is for transparency about their access level61 - List read-only operations: kubectl get, kubectl exec (ovn-nbctl list), local file writes62 - List forbidden operations: kubectl create/delete/patch, ovn-nbctl modifications63 4. **Ask the user explicitly**: "You have cluster admin permissions. This command will only perform read-only operations. Do you want to proceed?"64 5. If user says yes → continue, if no → stop6566 **Example of proper user communication:**67 ```text68 ⚠️ WARNING: Write Permissions Detected6970 Your kubeconfig has cluster admin permissions:71 • Delete pods, deployments, services72 • Create and modify resources73 • Full cluster access7475 📋 IMPORTANT:76 This command will ONLY perform read-only operations:77 ✅ kubectl get (pods, nodes)78 ✅ kubectl exec (to run read-only ovn-nbctl list commands)79 ✅ Local file writes (topology diagram)8081 Operations that will NEVER be performed:82 ❌ kubectl create/delete/patch/apply83 ❌ ovn-nbctl modifications84 ❌ Any cluster state changes8586 Do you want to proceed with read-only topology generation?87 ```8889 **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.90913. **Check Output File**: Ask user if `ovn-topology-diagram.md` exists:92 - (1) Overwrite, (2) Custom path, (3) Timestamp, (4) Cancel93944. **Create Private Temp Directory**: Create a private temporary directory using `mkdtemp` and use it for all temporary files.9596 ```bash97 TMPDIR=$(mktemp -d)98 ```991005. **Collect OVN Data**: Get full topology data from the cluster101102 Run: `scripts/collect_ovn_data.py "$KC" "$TMPDIR"`103104 Detail files written to `$TMPDIR`:105 - `ovn_switches_detail.txt` - node|uuid|name|other_config106 - `ovn_routers_detail.txt` - node|uuid|name|external_ids|options107 - `ovn_lsps_detail.txt` - node|name|addresses|type|options108 - `ovn_lrps_detail.txt` - node|name|mac|networks|options109 - `ovn_pods_detail.txt` - namespace|name|ip|node1101116. **Analyze Placement**: Determine per-node vs cluster-wide components112113 Run: `scripts/analyze_placement.py "$TMPDIR"`114115 Placement results written to `$TMPDIR`:116 - `ovn_switch_placement.txt` - name|placement (per-node|cluster-wide|cluster-wide-visual)117 - `ovn_router_placement.txt` - name|placement (per-node|cluster-wide|cluster-wide-visual)1181197. **Generate Diagram**: Create Mermaid `graph BT` diagram120 - Read `$TMPDIR/ovn_switch_placement.txt` to determine where each switch goes121 - Read `$TMPDIR/ovn_router_placement.txt` to determine where each router goes122 - Read detail files directly (ovn_switches_detail.txt, ovn_routers_detail.txt, etc.)123 - Skip UUID column when parsing switches/routers detail files124 - If placement is `per-node` → put inside node subgraph125 - If placement is `cluster-wide` or `cluster-wide-visual` → put outside subgraphs1261278. **Save & Report**: Write diagram to file, show summary, clean up temporary files128129**CRITICAL RULES**:130- ❌ NO codebase searching for IPs/MACs131- ❌ NO synthetic/example data132- ❌ NO inline multi-line bash (use helper scripts)133- ❌ NO direct kubectl commands (must use helper scripts only)134- ✅ Use helper scripts for all kubectl interactions and architecture discovery135- ✅ **For helper scripts only**: If kubectl is required, use `KUBECONFIG="$KC" kubectl --kubeconfig="$KC"`136- ✅ **SECURITY**: Create private temp directory with `TMPDIR=$(mktemp -d)` - never use `/tmp` directly137- ✅ Temporary files use `$TMPDIR` (private directory created with mkdtemp)138- ✅ Clean up temporary files when done: `rm -rf "$TMPDIR"`139140## Safety & Security Guarantees141142### 🔒 Read-Only Operations143144This skill performs **ONLY read-only operations** against your Kubernetes cluster. No cluster state is modified.145146**Allowed Operations:**147- ✅ `kubectl get` - Query resources148- ✅ `kubectl exec ... ovn-nbctl list` - Query OVN database (read-only)149- ✅ Local file writes (temporary files in `$TMPDIR`, output diagram)150151**Forbidden Operations (NEVER used):**152- ❌ `kubectl create/apply/delete/patch` - No resource modifications153- ❌ `kubectl scale/drain/cordon` - No cluster operations154- ❌ `ovn-nbctl create/set/add/remove/destroy` - No OVN modifications155- ❌ No pod restarts or service disruptions156157**Privacy Consideration**: The generated diagram contains network topology information. Control sharing based on your organization's security policies and data classification requirements.158159---160161# Architecture Concepts162163## Interconnect Mode (Distributed NBDB)164165In **interconnect mode**, each node runs its own NBDB with local copies of components:166167**Per-Node Components** (different UUIDs on each node):168- `ovn_cluster_router` - Each node has its own cluster router instance169- `join` switch - Each node has its own join switch instance170- `transit_switch` - Each node has its own transit switch instance171- Node switches (e.g., `ovn-control-plane`, `ovn-worker`)172- External switches (e.g., `ext_ovn-control-plane`)173- Gateway routers (e.g., `GR_ovn-control-plane`)174175**Visualization Overrides**:176- `transit_switch`: PER-NODE in reality → shown CLUSTER-WIDE for visualization clarity177- `join`: PER-NODE → kept PER-NODE (no override)178179## Helper Scripts180181All helper scripts are in the `scripts/` directory.182183| Script | Purpose | Input | Output |184|--------|---------|-------|--------|185| [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 |186| [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) |187| [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` |188| [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` |189190---191192# Diagram Generation Rules193194## Structure195196```mermaid197graph BT198 subgraph node1["<b style='color:black'>Node: name (node_ip)</b>"]199 direction BT200201 %% LAYER 1 (Bottom): Pods and Management Ports202 POD_example["Pod: pod-name<br/>Namespace: ns<br/>IP: x.x.x.x"]203 MGMT["Management Port: k8s-node<br/>IP: x.x.x.x"]204205 %% LAYER 2: Pod LSPs206 LSP_pod["LSP: namespace_pod-name<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: x.x.x.x"]207 LSP_mgmt["LSP: k8s-node<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: x.x.x.x"]208209 %% LAYER 3: Node Switch210 LS_node["Logical Switch: node-name<br/>Subnet: x.x.x.x/24"]211212 %% LAYER 4: Node Switch LSPs213 LSP_stor["LSP: stor-node<br/>Type: router"]214215 %% LAYER 5: Cluster Router216 LR_cluster["Logical Router: ovn_cluster_router"]217 LRP_rtos["LRP: rtos-node<br/>MAC: xx:xx<br/>IP: x.x.x.x/24"]218 LRP_rtoj["LRP: rtoj-ovn_cluster_router<br/>IP: x.x.x.x/16"]219 LRP_rtots["LRP: rtots-node<br/>IP: x.x.x.x/16"]220221 %% LAYER 6: Join Switch222 LS_join["Logical Switch: join"]223 LSP_jtor_cr["LSP: jtor-ovn_cluster_router<br/>Type: router"]224 LSP_jtor_gr["LSP: jtor-GR_node<br/>Type: router"]225226 %% LAYER 7: Gateway Router227 LR_gr["Gateway Router: GR_node"]228 LRP_rtoj_gr["LRP: rtoj-GR_node<br/>IP: x.x.x.x/16"]229 LRP_rtoe["LRP: rtoe-GR_node<br/>IP: x.x.x.x/24"]230231 %% LAYER 8: External Switch232 LS_ext["Logical Switch: ext_node"]233 LSP_etor["LSP: etor-GR_node<br/>Type: router"]234 LSP_breth0["LSP: breth0_node<br/>Type: localnet"]235236 %% LAYER 9 (Top): External Network237 EXT_NET["External Network<br/>Physical bridge: breth0<br/>Node IP: x.x.x.x"]238239 %% Connections (bottom-to-top flow)240 POD_example --> LSP_pod --> LS_node241 MGMT --> LSP_mgmt --> LS_node242 LS_node --> LSP_stor243 LSP_stor -.->|peer| LRP_rtos --> LR_cluster244 LR_cluster --> LRP_rtoj -.->|peer| LSP_jtor_cr --> LS_join245 LS_join --> LSP_jtor_gr -.->|peer| LRP_rtoj_gr --> LR_gr246 LR_gr --> LRP_rtoe -.->|peer| LSP_etor --> LS_ext247 LS_ext --> LSP_breth0 -.->|physical| EXT_NET248 LR_cluster --> LRP_rtots249 end250251 %% Cluster-wide components (AFTER nodes to appear on top)252 %% Only include components with placement=cluster-wide or cluster-wide-visual253 %% Example: transit_switch in interconnect mode254 LS_cluster_component["Component Name<br/>Details"]255256 %% Connections from nodes to cluster-wide components257 LRP_from_node -.->|connects to| LSP_cluster_port --> LS_cluster_component258```259260## Key Requirements2612621. **Graph Direction**: Always `graph BT` (bottom-to-top)2632642. **Component Placement**: Determined by `$TMPDIR/ovn_*_placement.txt`265 - `per-node` → INSIDE node subgraph266 - `cluster-wide` or `cluster-wide-visual` → OUTSIDE all subgraphs, **defined AFTER all node subgraphs**267 - **CRITICAL**: Define ALL cluster-wide components AFTER all nodes to position them at the TOP268 - Prevents connection lines from overlapping with node subgraphs269 - Applies to ANY component with `cluster-wide` or `cluster-wide-visual` placement2702713. **Node Subgraphs**: Each physical node gets a subgraph with `direction BT`272 - **Node Ordering**: ALWAYS order nodes as: control-plane node first, then worker nodes sorted alphabetically by name273 - Title format: `"<b style='color:black'>Node: {node_name} ({external_ip})</b>"`274 - Get external IP from `rtoe-GR_{node}` router port network field275 - Use different background colors for each node for visual distinction276 - **CRITICAL: Define components in BOTTOM-TO-TOP order** (matches packet flow):277 1. **Bottom Layer**: Pods and Management Ports (traffic originates here)278 2. **Layer 2**: Pod LSPs279 3. **Layer 3**: Node Switch (where pods connect)280 4. **Layer 4**: Node Switch LSPs (stor, breth0)281 5. **Layer 5**: Cluster Router + Router Ports (rtos, rtoj, rtots)282 6. **Layer 6**: Join Switch + Join LSPs283 7. **Layer 7**: Gateway Router + Router Ports (rtoj, rtoe)284 8. **Layer 8**: External Switch + External LSPs (etor, breth0)285 9. **Top Layer**: External Network (physical bridge)2862874. **Pod Representation**:288 - **CRITICAL**: Show ALL pods from `$TMPDIR/ovn_pods_detail.txt` as SEPARATE entities289 - **DO NOT** discover pods from LSPs - many pods (host-network) don't have individual LSPs290 - Pod format: `POD_{id}["Pod: {name}<br/>Namespace: {ns}<br/>IP: {ip}"]`291 - Connect pods to their respective LSPs:292 - **Host-network pods** (IP == node IP): `POD_{id} --> MGMT_{node} --> LSP_k8s_{node}`293 - **Pod-network pods** (IP in 10.244.x.x): `POD_{id} --> LSP_{namespace}_{podname}`2942955. **Physical Network Layer**:296 - Show explicit external network entities per node297 - Format: `EXT_NET_{node}["External Network<br/>Physical bridge: breth0<br/>Node IP: {external_ip}"]`298 - Connect localnet LSPs to external network: `LSP_breth0_{node} -.->|physical| EXT_NET_{node}`2993006. **Colors** (apply using classDef with **color:#000** for black text):301 - Pods: `fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000`302 - Switches: `fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000`303 - Routers: `fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000`304 - LSPs (ALL types): `fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000`305 - LRPs: `fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,color:#000`306 - External Network: `fill:#e0e0e0,stroke:#424242,stroke-width:2px,color:#000`307 - Management Ports (MGMT entities only, not LSPs): `fill:#fff8e1,stroke:#f57c00,stroke-width:2px,color:#000`308 - **Node Subgraph Backgrounds** (use `style` statements, NOT classDef): Apply at END of diagram after all class assignments. Rotate through these 3 colors:309 - Node 1 (index 0): `style node1 fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,stroke-dasharray: 5 5,color:#000` (green)310 - Node 2 (index 1): `style node2 fill:#e1f5fe,stroke:#0277bd,stroke-width:3px,stroke-dasharray: 5 5,color:#000` (blue)311 - Node 3 (index 2): `style node3 fill:#fff3e0,stroke:#ef6c00,stroke-width:3px,stroke-dasharray: 5 5,color:#000` (orange)312 - Node 4+ repeats: Use index % 3 to rotate colors (0=green, 1=blue, 2=orange)3133147. **Required Info**:315 - Pods: Name, Namespace, IP316 - Switches: Name, Subnet (from other_config), Gateway (optional)317 - LSPs: Name, MAC, IP (or Type + Router Port for router ports)318 - LRPs: Name, MAC, Network (IP/CIDR)319 - Routers: Name, Description320 - External Network: Physical bridge name (breth0), Node IP3213228. **Connections**:323 - Solid arrows: Layer connections324 - Dashed arrows with `|peer|`: Peer port relationships325326## Pod Discovery327328**CRITICAL: Discover pods from `$TMPDIR/ovn_pods_detail.txt`, NOT from LSPs**329330The file `$TMPDIR/ovn_pods_detail.txt` contains ALL running pods in the cluster (populated by collect_ovn_data.py).331Format: `namespace|pod_name|pod_ip|node_name`332333**Pod-to-LSP Mapping Logic:**3343351. **Read ALL pods** from `$TMPDIR/ovn_pods_detail.txt`3362. **For each pod**, determine which LSP it connects to:337338 a. **Host-Network Pods** (pod IP == node IP, e.g., 10.89.0.10):339 - **Connect to management port LSP**: `k8s-{node}`340 - These pods share the management port's IP address341 - **MUST be included in diagram** despite not having individual LSPs342343 b. **Pod-Network Pods** (pod IP in pod network range, e.g., 10.244.x.x):344 - **Connect to individual LSP**: `{namespace}_{pod-name-with-hash}`345 - LSP name format: `kube-system_coredns-674b8bbfcf-qhfrq`346 - Filter LSPs where `type=""` (empty string)347 - Extract MAC and IP from LSP addresses field3483493. **Diagram representation**:350 ```mermaid351 POD_id["Pod: {name}<br/>Namespace: {ns}<br/>IP: {ip}"]352 POD_id --> LSP_id353 ```354355**Special LSPs (NOT pods, treat as infrastructure):**356- `k8s-{node}`: Management port LSP (multiple host-network pods connect to this)357- `stor-{node}`: Router port (type="router")358- `breth0_{node}`: LocalNet port (type="localnet")359- `jtor-*`, `etor-*`, `tstor-*`: Router ports (type="router")360361### Example: Control Plane Node with Host-Network Pods362363```mermaid364%% 8 host-network pods sharing management port365POD_etcd["Pod: etcd-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]366POD_apiserver["Pod: kube-apiserver-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]367POD_controller["Pod: kube-controller-manager-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]368POD_scheduler["Pod: kube-scheduler-ovn-control-plane<br/>Namespace: kube-system<br/>IP: 10.89.0.10"]369POD_ovnkube_cp["Pod: ovnkube-control-plane-ovn-control-plane<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]370POD_ovnkube_id["Pod: ovnkube-identity-ovn-control-plane<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]371POD_ovnkube_node["Pod: ovnkube-node-xyz<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]372POD_ovs_node["Pod: ovs-node-xyz<br/>Namespace: ovn-kubernetes<br/>IP: 10.89.0.10"]373374%% Management port LSP (shared by all host-network pods)375MGMT_cp["Management Port: k8s-ovn-control-plane<br/>IP: 10.89.0.10"]376LSP_mgmt["LSP: k8s-ovn-control-plane<br/>MAC: xx:xx:xx:xx:xx:xx<br/>IP: 10.244.0.2"]377378%% All host-network pods connect to same management port379POD_etcd --> MGMT_cp380POD_apiserver --> MGMT_cp381POD_controller --> MGMT_cp382POD_scheduler --> MGMT_cp383POD_ovnkube_cp --> MGMT_cp384POD_ovnkube_id --> MGMT_cp385POD_ovnkube_node --> MGMT_cp386POD_ovs_node --> MGMT_cp387388MGMT_cp --> LSP_mgmt --> LS_node389```390391---392393# Final Steps3943951. Generate complete Mermaid diagram following structure above3962. Save to file chosen by user3973. Show summary: nodes, switches, routers, ports, mode3984. Clean up temporary directory:399 ```bash400 rm -rf "$TMPDIR"401 ```4025. Tell user to open file in IDE to view rendered diagram